
死锁的现象
想象一个场景,账户A给账户B转账,同时账户B也给账户A转账,两个账户都需要锁住余额,所以通常会申请两把锁,转账时,先锁住自己的账户,并获取对方的锁,保证同一时刻只能有一个线程去执行转账。
这时可能就会出现,对方给我转账,同时我也给对方转账,那么双方都持有自己的锁,且尝试去获取对方的锁,这就造成可能一直申请不到对方的锁,循环等待,就会发生“死锁”。
一旦发生死锁,线程一直占用着资源无法释放,又无法完成转账,就会造成系统假死。
什么是死锁?
“死锁”就是两个或两个以上的线程在执行过程中,互相持有对方所需要的资源,导致这些线程处于等待状态,无法继续执行。若无外力作用,它们都将无法继续执行下去,就进入了“永久”阻塞的状态。

图1 死锁的现象
如图所示,线程1获取了资源1,同时去请求获取资源2,但是线程2已经占有资源2了,所以线程1只能等待。同样的,线程2占有了资源2,要请求获取资源1,但资源1已经被线程1占有了,只能等待。于是线程1和线程2都在等待持有对方的持有的资源,就会无限等待下去,这就是死锁现象。
模拟发生死锁的场景
下面写一段代码,模拟两个线程各自持有了锁,然后请求获取对方持有的锁,发生死锁的现象。
public class DeadLock {public static String obj1 = "obj1";public static String obj2 = "obj2";public static void main(String[] args) {Thread a = new Thread(new Lock1());Thread b = new Thread(new Lock2());a.start();b.start();}static class Lock1 implements Runnable {public void run() {try {System.out.println("Lock1 running");synchronized (DeadLock.obj1) {System.out.println("Lock1 lock obj1");Thread.sleep(5000);synchronized (DeadLock.obj2) {System.out.println("Lock1 lock obj2");}}} catch (Exception e) {e.printStackTrace();}}}static class Lock2 implements Runnable {public void run() {try {System.out.println("Lock2 running");synchronized (DeadLock.obj2) {System.out.println("Lock2 lock obj2");Thread.sleep(5000);synchronized (DeadLock.obj1) {System.out.println("Lock2 lock obj1");}}} catch (Exception e) {e.printStackTrace();}}}}
程序启动后,从控制台输出,就能看出两个线程都没有结束,而是被卡住了。

图2 死锁demo输出
我们用jvisualVM看下线程的堆栈信息:

图3 jvisualVM堆栈信息
我们用jvisualVM查看线程的堆栈信息,发现已经检测到了死锁的存在,而且定位到了具体的代码行。
死锁产生的原因
死锁的发生也必须具备一定的条件,必须具备以下四个条件:
互斥,共享资源 X 和 Y 只能被一个线程占用; 占有且等待,线程01 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X; 不可抢占,其他线程不能强行抢占线程01 占有的资源; 循环等待,线程01 等待线程02 占有的资源,线程02 等待线程01 占有的资源,就是循环等待。
如何避免死锁?
首先,“互斥”是没有办法避免的,你想从账户A转账到账户B,就必须加锁,就没法避免互斥的存在。 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以在一定时间后,主动释放它占有的资源,这样就解决了不可抢占这个条件。 对于“循环等待”,我们可以靠按“次序”申请资源来预防。所谓按序申请,就是给资源设定顺序,申请的时候可以先申请序号小的资源,再申请序号大的,这样资源线性化后,自然就不存在循环等待了。
public class DeadLock2 {public static void main(String[] args) {Account a = new Account();Account b = new Account();a.transfer(b, 100);b.transfer(a, 200);}static class Allocator {private List<Account> als = new ArrayList<>();private void Allocator() {}synchronized boolean apply(Account from, Account to) {if (als.contains(from) || als.contains(to)) {return false;} else {als.add(from);als.add(to);}return true;}synchronized void clean(Account from, Account to) {als.remove(from);als.remove(to);}}static class Account {private Allocator actr = DeadLock2.getInstance();private int balance;void transfer(Account target, int amt) {while (!actr.apply(this, target)){}try {synchronized (this) {System.out.println(this.toString() + " lock lock1");synchronized (target) {System.out.println(this.toString() + " lock lock2");if (this.balance > amt) {this.balance -= amt;target.balance += amt;}}}} finally {actr.clean(this, target);}}}private static class SingleTonHoler {private static Allocator INSTANCE = new Allocator();}public static Allocator getInstance() {return SingleTonHoler.INSTANCE;}}

图4 破坏占用且等待条件输出
破坏不抢占条件,需要发生死锁的线程能够主动释放它占有的资源,但使用synchronized是做不到的。原因为synchronized申请不到资源时,线程直接进入了阻塞状态,而线程进入了阻塞状态也就没有办法释放它占有的资源了。
不过JDK中的Lock解决这个问题。
public class DeadLock3 {public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();public static void main(String[] args) {Thread a = new Thread(new Lock1());Thread b = new Thread(new Lock2());a.start();b.start();}static class Lock1 implements Runnable {@Overridepublic void run() {try {System.out.println("Lock1 running");while (true) {if (lock1.tryLock(1, TimeUnit.MILLISECONDS)) {System.out.println("Lock1 get lock1");if (lock2.tryLock(1, TimeUnit.MILLISECONDS)) {System.out.println("Lock·get lock2");return;}}}} catch (Exception e) {e.printStackTrace();} finally {lock1.unlock();lock2.unlock();}}}static class Lock2 implements Runnable {@Overridepublic void run() {try {System.out.println("Lock2 running");while (true) {if (lock1.tryLock(1, TimeUnit.MILLISECONDS)) {System.out.println("Lock2 get lock1");if (lock2.tryLock(1, TimeUnit.MILLISECONDS)) {System.out.println("Lock2 get lock2");return;}}}} catch (Exception e) {e.printStackTrace();} finally {lock1.unlock();lock2.unlock();}}}}
输出结果如下:

图5 破坏不可抢占条件输出
3、破坏循环等待条件
class Account {private int id;private int balance;void transfer(Account target, int amt){Account left = this;Account right = target;if (this.id > target.id) {left = target;right = this;}synchronized(left){synchronized(right){if (this.balance > amt){this.balance -= amt;target.balance += amt;}}}}}