当需要在应用方法中获取多个锁时,必须非常小心控制锁的顺序,错误的选择会导致死锁情形。
本节将实现死锁情形的范例,然后学习如何解决死锁。
实现过程
通过如下步骤实现本范例:
-
创建名为BadLocks的类,包含两个方法,名为operation1()和operation2():
public class BadLocks { private Lock lock1, lock2; public BadLocks(Lock lock1, Lock lock2) { this.lock1=lock1; this.lock2=lock2; } public void operation1(){ lock1.lock(); lock2.lock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock2.unlock(); lock1.unlock(); } } public void operation2(){ lock2.lock(); lock1.lock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock1.unlock(); lock2.unlock(); } } }
-
分析下这段代码,如果线程调用operation1()方法,并且另一个线程调用operation2()方法,则造成死锁。如果operation1()和operation2()同时执行各自的第一行代码,operation1()方法等待控制lock2,而operation2()方法等待控制lock1。 这就造成了死锁情形。
-
为了解决死锁,遵循如下规则:
- 如果必须在不同的操作中控制多个锁,试图在所有方法中以相同的顺序锁定它们。
- 然后,按照相反的顺序释放锁,并将锁及其解锁封装在一个类中。这样就不会在整个代码中分布与同步相关的代码。
工作原理
使用此规则,将避免死锁情形。例如本范例中,改变operation2()方法,首先得到lock1,然后lock2。现在如果operation1()和operation2()同时执行各自的第一行代码,其中一个方法将被阻塞等待lock1,另一个方法得到lock1和lock2,并执行操作。之后被阻塞的线程将获得lock1和lock2锁,并执行其操作。
扩展学习
开发过程中,可能会出现在所有操作中阻止按照相同顺序获取锁的情况,这时需要使用Lock类的tryLock()方法。此方法返回Boolean值,指明是否具有锁的控制权。使用tryLock()方法尝试获取执行操作需要的所有锁。如果无法控制其中一个锁,则必须释放可能拥有的所有锁,并重新启动操作。
更多关注
- 本章“保持锁的时间尽可能短”小节