java避免活锁.死锁的解决,死锁 活锁 饥饿 出现原因及解决方案

文章目录

死锁

概念

死锁示例

为什么会出现死锁呢?

如何解决死锁呢?

解决死锁代码实现

活锁

概念

活锁示例:

如何解决活锁呢?

饥饿

概念

如何解决饥饿呢?

死锁

概念

死锁:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

说白了就是:两个线程互相持有对方所需的资源,互不释放且互相等待

死锁示例

/**

* 死锁

*

* @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("转账结束");

}

}

}

}

执行结果

a1f767c21417e8f3c63bf78d04357d3b.png

为什么会出现死锁呢?

假设线程T1执行转账A->B,线程T2执行转账B->A,两个线程同时执行synchronized (this)时,线程T1获取了A的锁,线程T2获取了B的锁,同时又执行到了synchronized (target)时,线程T1获取B锁时,发现B锁已经被线程T2持有,线程T1进入阻塞状态;与此同时,线程T2获取A锁时,发现A锁已经被线程T1持有,线程T2也进入阻塞状态。参考下图理解

257899e6200789af1735ac04f49ec7db.png

如何解决死锁呢?

并发程序一旦死锁,一般没有特别好的方法,很多时候我们只能重启应用。因此,解决死锁问题最好的办法还是规避死锁。

那如何避免死锁呢?要避免死锁就需要分析死锁发生的条件,有个叫 Coffman 的牛人早就总结过了,只有以下这四个条件都发生时才会出现死锁:

互斥,共享资源 X 和 Y 只能被一个线程占用;

占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;

不可抢占,其他线程不能强行抢占线程 T1 占有的资源;

循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

既然知道了出现死锁的必要条件,其实只要破坏其中一条就可避免死锁!

其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的,到底如何做呢?

对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。

对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。

对于“循环等待”这个条件,可以按顺序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。

解决死锁代码实现

下面我只列出避免死锁的第三种方案按照顺序加锁,同时也是成本最低的。

//转账

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岁老人:“操,刚刚进去的就是我儿子! ”

bfd87d27da5990ee0931b4a859835d98.png

如果觉得不错,帮忙点个赞,您的点赞将是我的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值