Android线程篇(十一)之使用Synchronized导致的死锁

Synchronized对于大家来说工作中经常会用到,使用起来也很简单,这里就不来讲解它的基本用法,常常听说Synchronized使用不当会导致死锁情况发生,到底什么是死锁?我们今天就来一探究竟。。。
本文基本是照抄过来的,因为写的实在是太好了,我找不到更好的办法来描述这个问题,原版地址:http://ifeve.com/deadlock/

死锁就是俩个或者俩个以上的线程阻塞着,并且在等待其他死锁线程持有的锁。

貌似有些绕,没关系,来来来:

Thread 1  locks A, waits for B
Thread 2  locks B, waits for A

是不是有些明白了??

还不明白?没关系,For example:

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

如果线程1调用addChild(child)方法的同时有另外一个线程2调用setParent(parent)方法,两个线程中的parent表示的是同一个对象,child亦然,此时就会发生死锁。

Thread 1: parent.addChild(child); //locks parent
          --> child.setParentOnly(parent);

Thread 2: child.setParent(parent); //locks child
          --> parent.addChildOnly()

首先线程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加锁的时候被阻塞,这样死锁就不会发生。因为线程调度通常是不可预测的,因此没有一个办法可以准确预测什么时候死锁会发生,仅仅是可能会发生。

更复杂的死锁

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.

因为锁发生在不同的请求中,并且对于一个事务来说不可能提前知道所有它需要的锁,因此很难检测和避免数据库事务中的死锁。

你可能感兴趣的:(Android线程篇(十一)之使用Synchronized导致的死锁)