上面的代码看起来都是按照相同的顺序来获得锁的,按道理来说是没有问题,但是上述代码中上锁的顺序取决于传递给transferMoney()的参数顺序,而这些参数顺序又取决于外部的输入
- 如果两个线程(A和B)同时调用transferMoney()
- 其中一个线程(A),从X向Y转账:transferMoney(myAccount,yourAccount,10);
- 另一个线程(B),从Y向X转账 :transferMoney(yourAccount,myAccount,20);
- 此时 A线程 可能获得 myAccount 的锁并等待 yourAccount的锁,然而 B线程 此时已经持有 yourAccount 的锁,并且正在等待 myAccount 的锁,这种情况下就会发生死锁 。
当一组JAVA线程发生死锁的时候,那么这些线程永远不能再使用了,根据线程完成工作的不同,可能会造成应用程序的完全停止,或者某个特定的子系统不能再使用了,或者是性能降低,这个时候恢复应用程序的唯一方式就是中止并重启它,死锁造成的影响很少会立即显现出来,如果一个类发生死锁,并不意味着每次都会发生死锁,而只是表示有可能,当死锁出现的时候,往往是在最糟糕的时候——在高负载的情况下 。
四、死锁的避免与检测
4.1 预防死锁
- 破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁
- 破坏请求和保持条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行,只要有一个资源得不到分配,也不给这个进程分配其他的资源 。
- 破坏不剥夺条件:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源,但是只适用于内存和处理器资源 。
- 破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行 。
4.2 设置加锁顺序
如果两个线程(A和B),当A线程已经锁住了Z,而又去尝试锁住X,而X已经被线程B锁住,线程A和线程B分别持有对应的锁,而又去争夺其他一个锁(尝试锁住另一个线程已经锁住的锁)的时候,就会发生死锁,如下图:

文章插图
两个线程试图以不同的顺序来获得相同的锁,如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,因此也就不会产生死锁,每个需要锁Z和锁X的线程都以相同的顺序来获取Z和X,那么就不会发生死锁了,如下图所示:

文章插图
- 这样死锁就永远不会发生 。针对两个特定的锁,可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,我们通过设置锁的顺序,来防止死锁的发生,在这里我们使用System.identityHashCode方法来定义锁的顺序,这个方法将返回由Obejct.hashCode 返回的值,这样就可以消除死锁发生的可能性 。
public class DeadLockExample3 { // 加时赛锁,在极少数情况下,如果两个hash值相等,使用这个锁进行加锁 private static final Object tieLock = new Object(); public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException { class Helper { public void transfer() throws InsufficientFundsException { if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAcct.debit(amount); toAcct.credit(amount); } } } // 得到两个锁的hash值 int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); // 根据hash值判断锁顺序,决定锁的顺序 if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else {// 如果两个对象的hash相等,通过tieLock来决定加锁的顺序,否则又会重新引入死锁——加时赛锁 synchronized (tieLock) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } } }}
- 在极少数情况下,两个对象可能拥有两个相同的散列值,此时必须通过某种任意的方法来决定锁的顺序,否则可能又会重新引入死锁 。
- 为了避免这种情况,可以使用 “加时(Tie-Breaking))”锁,这获得这两个Account锁之前,从而消除了死锁发生的可能性
4.3 支持定时的锁(超时放弃)
有一项技术可以检测死锁和从死锁中恢复过来,就是使用Lock类中的定时public boolean tryLock(long time, TimeUnit unit) throws InterruptedException功能,来代替内置锁机制,当使用内置锁的时候,只要没有获得锁,就会永远等待下去,而tryLock可以指定一个超时时间(Timeout),在等待超过时间后tryLock会返回一个失败信息,如果超时时限比获取锁的时间要长很多,那么就可以在发生某个意外后重新获得控制权 。如下图所示:
推荐阅读
-
ETtoday健康云TB|不只会中暑!医解析夏季高温危害:也会突发心肌梗塞
-
「奇葩搞笑菌」同学们要弹劾班主任,让作为班干部的我最先签,开心一笑:初中时
-
「旺见情感美文」入坑不亏,最热门最值得入手的鞋款!你值得拥有!,NIKE联名爆款
-
张大仙|张大仙人设崩塌?让粉丝在线喷人,得知原因后:你可真是个机灵鬼
-
央视新闻|美加州山火持续蔓延,多处葡萄园和酒庄遭毁
-
空调|空调新国标实施,近5成产品或遭淘汰,价格战能否叫停?
-
-
华为松口气?豪威获准继续供货图像传感器,母公司股价周五涨7%
-
明朝|大明王朝:中层干部有多难,严嵩一封信,胡宗宪被逼到去寻死!
-
仨三搞笑说|为什么不选大众的车子了?这位开了25万公里的帕萨特车主这么说
-
叶璇|直播被侮辱数小时,叶璇疯狂反击,晒对方自拍及家人照却被群攻
-
电竞皮皮风|皮肤销量突破百万,只因梦泪发现大招隐藏属性!,张飞虎魄上线仅一小时
-
上海乐天心理咨询|爸妈不打孩子,还能怎么办?,熊孩子很欠揍
-
#辽宁男篮#郭士强当真签了水货?梅奥首秀表现不及高诗岩,辽宁队9分负新疆!
-
乐观的小刚科技|8+128G+骁龙865+VC液冷散热+4780mAh,最低跌至3999,小米10真香
-
-
车家号|离全自动驾驶再近一步!凯迪拉克CT6超级巡航型试驾
-
文汇报|上海国际电影电视节今年首次开放论坛、大师班等活动的线上播出
-
-
上海市知识产权局网站|知识产权发展促进处党支部组织“四史”学习教育专题学习会