`
85977328
  • 浏览: 1873307 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发(十)死锁 & 活锁

 
阅读更多
活锁:一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有可能发生活锁。同死锁一样,发生活锁的线程无法继续执行。然而线程并没有阻塞——他们在忙于响应对方无法恢复工作。这就相当于两个在走廊相遇的人:Alphonse向他自己的左边靠想让Gaston过去,而Gaston向他的右边靠想让Alphonse过去。可见他们阻塞了对方。Alphonse向他的右边靠,而Gaston向他的左边靠,他们还是阻塞了对方。

死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。

例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。
该情况如下:
//Thread 1  locks A, waits for B
//Thread 2  locks B, waits for A
这里有一个TreeNode类的例子,它调用了不同实例的synchronized方法:

public class TreeNode {
    TreeNode parent   = null; 
    List children = new ArrayList();
    public synchronized void addChild(TreeNode child){
        if(!this.children.contains(child)) {
            this.children.add(child);
            child.setParentOnly(this);
        }
    }
    public synchronized void addChildOnly(TreeNode child){
        if(!this.children.contains(child){
            this.children.add(child);
        }
    }
    public synchronized void setParent(TreeNode parent){
        this.parent = parent;
        parent.addChildOnly(this);
    }
    public synchronized void setParentOnly(TreeNode parent){
        this.parent = parent;
    }
}

如果线程1调用parent.addChild(child)方法的同时有另外一个线程2调用child.setParent(parent)方法,两个线程中的parent表示的是同一个对象,child亦然,此时就会发生死锁。下面的伪代码说明了这个过程:
TreeNode parent = new TreeNode();
TreeNode child = new TreeNode();
Thread 1: parent.addChild(child); //locks parent
          --> child.setParentOnly(parent);

Thread 2: child.setParent(parent); //locks child
          --> parent.addChildOnly(child)
首先线程1调用parent.addChild(child)。因为addChild()是同步的,所以线程1会对parent对象加锁以不让其它线程访问该对象。

然后线程2调用child.setParent(parent)。因为setParent()是同步的,所以线程2会对child对象加锁以不让其它线程访问该对象。

现在child和parent对象被两个不同的线程锁住了。接下来线程1尝试调用child.setParentOnly()方法,但是由于child对象现在被线程2锁住的,所以该调用会被阻塞。线程2也尝试调用parent.addChildOnly(),但是由于parent对象现在被线程1锁住,导致线程2也阻塞在该方法处。现在两个线程都被阻塞并等待着获取另外一个线程所持有的锁。

注意:像上文描述的,这两个线程需要同时调用parent.addChild(child)和child.setParent(parent)方法,并且是同一个parent对象和同一个child对象,才有可能发生死锁。上面的代码可能运行一段时间才会出现死锁。

这些线程需要同时获得锁。举个例子,如果线程1稍微领先线程2,然后成功地锁住了A和B两个对象,那么线程2就会在尝试对B加锁的时候被阻塞,这样死锁就不会发生。因为线程调度通常是不可预测的,因此没有一个办法可以准确预测什么时候死锁会发生,仅仅是可能会发生。

更复杂的死锁
死锁可能不止包含2个线程,这让检测死锁变得更加困难。下面是4个线程发生死锁的例子:
Thread 1  locks A, waits for B
Thread 2  locks B, waits for C
Thread 3  locks C, waits for D
Thread 4  locks D, waits for A
线程1等待线程2,线程2等待线程3,线程3等待线程4,线程4等待线程1。

数据库的死锁
更加复杂的死锁场景发生在数据库事务中。一个数据库事务可能由多条SQL更新请求组成。当在一个事务中更新一条记录,这条记录就会被锁住避免其他事务的更新请求,直到第一个事务结束。同一个事务中每一个更新请求都可能会锁住一些记录。

当多个事务同时需要对一些相同的记录做更新操作时,就很有可能发生死锁,例如:

Transaction 1, request 1, locks record 1 for update
Transaction 2, request 1, locks record 2 for update
Transaction 1, request 2, tries to lock record 2 for update.
Transaction 2, request 2, tries to lock record 1 for update.
因为锁发生在不同的请求中,并且对于一个事务来说不可能提前知道所有它需要的锁,因此很难检测和避免数据库事务中的死锁。

另外一个死锁的例子
package com.chinaso.search.executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestLock {
    private static ExecutorService executor = Executors.newFixedThreadPool(10);
    private String name;

    /**
     * 同步方法1
     * @return
     */
    public synchronized String getName() {
        return name;
    }

    /**
     * 同步方法2
     * @param t
     */
    public synchronized void print(TestLock t) {
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println(name + " end sleep");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(t.getName() + " synchroinzed");
        }
    }

    public void setName(String name) {
        this.name = name;
    }

    public TestLock(String name) {
        this.name = name;
    }

    public static void main(String[] args) throws Exception {

        final TestLock t1 = new TestLock("t1");
        final TestLock t2 = new TestLock("t2");

        executor.execute(new Runnable() {
            @Override
            public void run() {
                t1.print(t2);
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                t2.print(t1);
            }
        });

    }
}

分享到:
评论
2 楼 85977328 2014-06-03  
request代表第几次请求
1 楼 ywu 2014-05-30  
额,数据库死锁的例子是不是有问题啊,我觉得应该是这样啊
Transaction 1, request 1, locks record 1 for update
Transaction 2, request 2, locks record 2 for update.
Transaction 1, request 1, tries to lock record 2 for update
Transaction 2, request 2, tries to lock record 1 for update.

相关推荐

    java高并发程序设计视频全集

    java高并发程序设计视频全集,并发场景,死锁,活锁,阻塞,非阻塞...

    JAVA高质量并发详解,多线程并发深入讲解

    此外,书中还深入剖析了并发编程中的常见问题,如死锁、活锁、饥饿等,并提供了相应的解决方案和最佳实践。 本书注重理论与实践相结合,通过大量的示例代码和案例分析,帮助读者更好地理解和掌握并发编程的技巧和...

    2万字Java并发编程面试题合集(含答案,建议收藏)

    2万字Java并发编程面试题合集(含答案,建议收藏) 具体如下 1、在 java 中守护线程和本地线程区别?2、线程与进程的区别?3、什么是多线程中的上下文切换?4、死锁与活锁的区别,死锁与饥饿的区别?5、Java 中用到...

    Java并发编程.docx

    o活跃性问题 :死锁、活锁、饥饿 o性能问题 : 使用无锁结构 :TLS,Copy-On-Write,乐观锁;Java的原子类,Disruptor无锁队列 减少锁的持有时间 :让锁 细粒 度。如ConcurrentHashmap;再如读写锁,读...

    Java并发编程(学习笔记).xmind

    Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...

    Java并发编程实战

    Java并发编程实战 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及...

    DiningPhilosophers:经典的并发问题,用Java解决

    餐饮哲学家经典的并发问题,用Java解决避免死锁、活锁和资源匮乏。

    leetcode安卓-Google-Interview:谷歌面试

    死锁,活锁 上下文切换 多核和“现代”并发构造 ###数学 离散数学 计数问题 90m 概率问题 90m ###安卓 一般90m 并发 60m Looper & Handler 120m ###Java 仿制药 60m 关闭 拉姆达 30m AOP 150m 并发 390m 动态...

    concurrent-programming:《实战java高并发程序设计》源码整理

    《实战java高并发程序设计》源码整理联系作者十三的java的学习交流QQ群: 881582471 , 658365129(已满)相关文章书籍封面目录第1章走入并行世界1.1何去何从的并行计算1.1.1忘掉那该死的并行1.1.2可怕的现实:摩尔...

    java8源码-highconcurrency:《实战Java高并发程序设计》葛一鸣、郭超着电子工业出版社2015年11月第一版书上部分代码

    介绍了并发的基础理论:同步/异步,并发/并行,临界区,阻塞/非阻塞,死锁/饥饿/活锁,并发级别,定律,JMM中的原子性,可见性,有序性等。 第二章Java并行程序基础: 介绍了thread的基本操作,volatile与...

    leetcode赛车-Concurrency:#JAVA#并发

    最常见的一种活性问题,死锁,以及另外两种活性问题,饥饿和活锁。 僵局 死锁描述了两个或多个线程被永远阻塞、互相等待的情况。 饥饿 饥饿描述了线程无法定期访问共享资源并且无法取得进展的情况。 当共享资源被...

    java8源码-javahighconcurrent:高并发

    源码《实战Java高并发程序设计》 上的代码 葛一鸣郭超着 书籍封面: 注意事项 再运行代码之前,先用IDE将lib/jmatrices0.6.jar导入项目依赖中,因为书籍的第五章用到了这个库,然而这个库在maven仓库中又没有找到,只好...

Global site tag (gtag.js) - Google Analytics