赞
踩
死锁:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。
说白了就是:两个线程互相持有对方所需的资源,互不释放且互相等待
/** * 死锁 * * @author m */ public class Account { private int balances; public static void main(String[] args) { Account A = new Account(); Account B = new Account(); new Thread(() -> { A.transfer(B, 100); }, "线程1 ").start(); new Thread(() -> { B.transfer(A, 100); }, "线程2 ").start(); } //转账 public void transfer(Account target, int money) { //休眠(放大问题发生性) try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this) { System.out.println(Thread.currentThread().getName() + " 持有锁 " + this + ",等待锁" + target); synchronized (target) { System.out.println(Thread.currentThread().getName() + " 持有锁 " + target); this.balances += money; target.balances -= money; System.out.println("转账结束"); } } } }
执行结果
假设线程T1执行转账A->B,线程T2执行转账B->A,两个线程同时执行synchronized (this)时,线程T1获取了A的锁,线程T2获取了B的锁,同时又执行到了synchronized (target)时,线程T1获取B锁时,发现B锁已经被线程T2持有,线程T1进入阻塞状态;与此同时,线程T2获取A锁时,发现A锁已经被线程T1持有,线程T2也进入阻塞状态。参考下图理解
并发程序一旦死锁,一般没有特别好的方法,很多时候我们只能重启应用
。因此,解决死锁问题最好的办法还是规避死锁
。
那如何避免死锁呢?要避免死锁就需要分析死锁发生的条件,有个叫 Coffman 的牛人早就总结过了,只有以下这四个条件都发生时才会出现死锁:
既然知道了出现死锁的必要条件,其实只要破坏其中一条就可避免死锁!
其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的,到底如何做呢?
一次性申请所有的资源
,这样就不存在等待了。占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源
,这样不可抢占这个条件就破坏掉了。按顺序申请资源
来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。下面我只列出避免死锁
的第三种方案按照顺序加锁
,同时也是成本最低的
。
//转账 public void transfer(Account target, int money) { Account little; //id小的账户 Account big; //id大的账户 if (this.id < target.id) { little = this; big = target; } else { little = target; big = this; } // 锁定id小的账户 synchronized (little) { System.out.println(Thread.currentThread().getName() + " 持有锁 " + this + ",等待锁" + target); // 锁定id大的账户 synchronized (big) { System.out.println(Thread.currentThread().getName() + " 持有锁 " + target); this.balances += money; target.balances -= money; System.out.println("转账结束"); } } }
活锁:有时线程虽然没有发生阻塞,但仍然会存在执行不下去的情况。
说白了就是:两个线程因互相礼让,导致线程永远的礼让下去
例如:可以类比现实世界里的例子,路人甲从左手边出门,路人乙从右手边进门,两人为了不相撞,互相谦让,路人甲让路走右手边,路人乙也让路走左手边,结果是两人又相撞了。这种情况,基本上谦让几次就解决了,因为人会交流啊。可是如果这种情况发生在编程世界了,就有可能会一直没完没了地“谦让”下去,成为没有发生阻塞但依然执行不下去的“活锁”。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 活锁 * * @author m */ public class Account2 { private int balances; private final Lock lock = new ReentrantLock(); public static void main(String[] args) { Account2 A = new Account2(); Account2 B = new Account2(); new Thread(() -> { A.transfer(B, 100); }, "线程1 ").start(); new Thread(() -> { B.transfer(A, 100); }, "线程2 ").start(); } void transfer(Account2 target, int money) { while (true) { if (this.lock.tryLock()) { System.out.println(Thread.currentThread().getName() + " 持有锁 " + this.lock + ",等待锁" + target.lock); try { if (tar.lock.tryLock()) { System.out.println(Thread.currentThread().getName() + " 持有锁 " + target.lock); try { this.balances -= money; tar.balances += money; } finally { tar.lock.unlock(); } } } finally { this.lock.unlock(); } } } } }
谦让时,尝试等待一个随机的时间
就可以了。“等待一个随机时间
”的方案虽然很简单,却非常有效,Raft 这样知名的分布式一致性算法中也用到了它。
例如上面的那个例子,路人甲走左手边发现前面有人,并不是立刻换到右手边,而是等待一个随机的时间后,再换到右手边;同样,路人乙也不是立刻切换路线,也是等待一个随机的时间再切换。由于路人甲和路人乙等待的时间是随机的,所以同时相撞后再次相撞的概率就很低了。
饥饿:线程因无法访问所需资源而无法执行下去的情况
说白了就是:假设有1万个线程,还没等前面的线程执行完,后面的线程就饿死了
下面提供了三种方案
这三个方案中,方案一和方案三的适用场景比较有限,因为很多场景下,资源的稀缺性是没办法解决的,持有锁的线程执行的时间也很难缩短。倒是方案二的适用场景相对来说更多一些。
开心一刻
两个老人去养老院。。。
70岁的老人进去了,90岁老人没进去。
工作人员:“对不起,大爷,我们不接受儿女健在的老人。您的资料显示,你有一个儿子。”
90岁老人:“操,刚刚进去的就是我儿子! ”
如果觉得不错,帮忙点个赞,您的点赞将是我的动力!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。