java 活锁 线程饿死,JAVA并发编程(四)线程死锁、饥饿、活锁

JAVA并发编程(四)线程死锁

线程死锁

什么是线程死锁呢?

为什么会线程死锁呢?

如何避免线程死锁?

什么是饥饿呢?

什么是活锁呢?

线程死锁

什么是线程死锁呢?

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的情况,并且在无外力作用的情况下,这些线程会一直相互的等待,而无法继续等待下去。

死锁应该是最糟糕的一种情况了(当然,其他几种情况也好不到哪里去),如下图显示了一个死锁的发生:

bca59c4ff62af117e4ec9e4d4363dafd.png

A、B、C、D四辆小车都在这种情况下都无法继续行驶了。他们彼此之间相互占用了其他车辆的车道,如果大家都不愿意释放自己的车道,那么这个状况将永远持续下去,谁都不可能通过,死锁是一个很严重的并且应该避免和实时小心的问题。

为什么会线程死锁呢?

学过操作系统的朋友应该都知道,死锁产生必需须具备的四个条件:

互斥条件:是指线程对已经获得的资源进行排他性使用。换而言之,该资源同时只能由一个线程占用。如果此时还有其他的线程请求该资源,那么请求者必须等待,直至占有该资源的线程释放该资源。

请求并持有条件:这个很好理解,一个线程已经持有了至少一个资源,但是又提出了新的资源请求,而新的资源又被其他线程占用,所以当前线程会被阻塞,但是被阻塞的线程又不释放已经占用资源。

不可剥夺条件:指的是线程获取到的资源在自己使用完之前不能被其他的线程抢夺,只能在自己使用完毕之后才能释放。

环路等待条件:指在发生线程死锁时,必然存在一个线程-资源的环形链。也就是线程集合{T0,T1,T2,T3……Tn}中的线程T0正在等待已被T1占用的资源,T1在等待已被T2占用的资源,……Tn正在等待已被T0占用的资源。

接下来举一个实际的死锁例子·:

public class DeadLockTest {

private static Object resourceA = new Object();

private static Object resourceB = new Object();

public static void main(String[] args) {

//创建并启动线程1

new Thread(new Runnable() {

@Override

public void run() {

synchronized (resourceA){

System.out.println(Thread.currentThread()+"get ResourceA");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread()+" wait get ResourceB");

synchronized (resourceB){

System.out.println(Thread.currentThread()+"get ResourceA");

}

}

}

}).start();

//创建并启动线程2

new Thread(new Runnable() {

@Override

public void run() {

synchronized (resourceB){

System.out.println(Thread.currentThread()+"get ResourceB");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread()+" wait get ResourceA");

synchronized (resourceA){

System.out.println(Thread.currentThread()+"get ResourceA");

}

}

}

}).start();

}

}

代码相对简单,这里不做过多阐述。

下面是执行结果:

659a27ce9625980bbeaaf3db6f77cde5.png

从执行结果来看,两个线程相互占用对方的资源,且不愿意释放,那么两个线程就会无休止地等待下去。

如何避免线程死锁?

要想避免线程死锁,至少要破坏一个构成线程死锁的条件。学过操作系统的都知道,目前只有请求并持有和环路等待是可以被破坏的。

我这里提供一种简单易理解的方法。通过资源的有序性申请来避免死锁,那么问题来了,什么是资源的有序性申请?

我们将刚才的线程死锁的例子的代码稍微改进一下,执行效果就会完全不一样,而且还避免了死锁。请看下面的代码:

public class DeadLockTest {

private static Object resourceA = new Object();

private static Object resourceB = new Object();

public static void main(String[] args) {

//创建并启动线程1

new Thread(new Runnable() {

@Override

public void run() {

synchronized (resourceA){

System.out.println(Thread.currentThread()+"get ResourceA");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread()+" wait get ResourceB");

synchronized (resourceB){

System.out.println(Thread.currentThread()+"get ResourceA");

}

}

}

}).start();

//创建并启动线程2

new Thread(new Runnable() {

@Override

public void run() {

synchronized (resourceA){

System.out.println(Thread.currentThread()+"get ResourceA");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread()+" wait get ResourceB");

synchronized (resourceB){

System.out.println(Thread.currentThread()+"get ResourceA");

}

}

}

}).start();

}

}

再来看执行结果:

d72a194408e914256af1fdd756f40f24.png

如上代码,线程2和线程1获取资源的顺序一致时,就不会发生死锁。其实资源的有序性分配就是指,假如线程1和线程2都需要资源1,2,3,,,n时,对资源进行排序,线程1和线程2只有获得了n-1时才能获得n。

什么是饥饿呢?

饥饿是指某一个或者多个线程因为种种原因无法获得所要的资源,导致一直无法执行。比如它的优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级线程无法工作。在自然界中,母鸡给雏鸟喂食很容易出现这种情况:由于雏鸟很多,食物有限,雏鸟之间的事务竞争可能非常厉害,经常抢不到事务的雏鸟有可能被饿死。线程的饥饿非常类似这种情况。此外,某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行,这种情况也是饥饿的一种。于死锁想必,饥饿还是有可能在未来一段时间内解决的(比如,高优先级的线程已经完成任务,不再疯狂执行)。

什么是活锁呢?

活锁是一种非常有趣的情况。不知道大家是否遇到过这么一种场景,当你要做电梯下楼时,电梯到了,门开了,这是你正准备出去。但很不巧的是,门外一个人当着你的去路,他想进来。于是,你很礼貌地靠左走,礼让对方。同时,对方也非常礼貌的靠右走,希望礼让你。结果,你们俩就又撞上了。于是乎,你们都意识到了问题,希望尽快避让对方,你立即向右边走,同时,他立即向左边走。结果,又撞上了!不过介于人类的智慧,我相信这个动作重复两三次后,你应该可以顺利解决这个问题。因为这个时候,大家都会本能地对视,进行交流,保证这种情况不再发生。但如果这种情况发生在两个线程之间可能就不那么幸运了。如果线程智力不够。且都秉承着“谦让”的原则,主动将资源释放给他人使用,那么久会导致资源不断地在两个线程间跳动,而没有一个线程可以同时拿到所有资源正常执行。这种情况就是活锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的死锁活锁是多线程并发编程中的常见问题,这些问题可能会导致线程无法向前执行或陷入死循环。下面分别介绍Java死锁活锁的概念和案例。 1. 死锁 死锁是指两个或多个线程相互等待对方释放锁,从而导致所有线程都无法向前执行的状态。简单来说,当两个或多个线程都占有某些资源并且又想要获取对方占有的资源时,就会发生死锁。 下面是一个Java中的死锁例子: ```java public class DeadlockExample { private Object lock1 = new Object(); private Object lock2 = new Object(); public void method1() { synchronized (lock1) { System.out.println("Method 1: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("Method 1: Holding lock 1 and lock 2..."); } } } public void method2() { synchronized (lock2) { System.out.println("Method 2: Holding lock 2..."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("Method 2: Holding lock 1 and lock 2..."); } } } public static void main(String[] args) { DeadlockExample example = new DeadlockExample(); Thread t1 = new Thread(() -> example.method1()); Thread t2 = new Thread(() -> example.method2()); t1.start(); t2.start(); } } ``` 在这个例子中,两个线程t1和t2分别调用了method1和method2方法,这两个方法都需要获取lock1和lock2两个锁才能继续执行。由于t1占用了lock1并等待lock2,而t2占用了lock2并等待lock1,因此两个线程都无法释放自己占用的锁,从而陷入死锁状态。 2. 活锁 活锁是指线程们都在运行并尝试执行任务,但是由于某些条件始终无法满足,导致线程们一直在重试,但是最终无法完成任务。这种情况下,线程们看起来像是在不断地活动,但是实际上却没有任何进展。 下面是一个Java中的活锁例子: ```java public class LiveLockExample { private boolean isPolite; public LiveLockExample(boolean isPolite) { this.isPolite = isPolite; } public void bow(LiveLockExample partner) { while (true) { if (isPolite) { System.out.println("I'm polite, I'll bow first"); partner.bowBack(this); isPolite = false; break;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值