Java中 死锁现象怎么解决_探讨Java中的死锁现象

本文通过一个简单的Java死锁代码示例,探讨了死锁产生的原因和四个必要条件:互斥、请求与保持、不剥夺和循环等待。提出了两种避免死锁的方法:线程休眠和使用join()。同时,分析了哲学家吃饭问题,说明了如何破坏循环等待条件以防止死锁。文章末尾提出了一道关于改进筷子分配策略以消除死锁的思考题。
摘要由CSDN通过智能技术生成

今天搞了一下Java的死锁机制,感觉自己还是不怎么懂,所以就从一些简单的源代码中琢磨:我先尝试写了一个很简单的死锁代码:思路是线程A获得B的锁但还没有获得C的锁,所以在等待获得C的锁,还线程A1获得了C的锁但没有获得B锁所以就在等待B的锁,所以就造成了相互等待,程序陷入死锁状态……

死锁程序:

public class DeadLock{

public static void main(String[] args) {

final Object a=new Object(),b=new Object();

Thread t1 = new Thread(new A(a,b));

Thread t2 = new Thread(new A1(a,b));

t1.start();

t2.start();

}

}

class  A implements Runnable{

private  Object B,C;

public A(Object B,Object C){

this.B=B;

this.C=C;

}

@Override

public void run() {

synchronized(B){ //获得B的锁

System.out.println("A线程获得B的锁等待C的锁");

synchronized(C){

System.out.println("A线程获得B的锁也获得了C的锁");

}

}

}

}

class  A1 implements Runnable{

private  Object B,C;

public A1(Object B,Object C){

this.B=B;

this.C=C;

}

@Override

public void run() {

synchronized(C){ //获得B的锁

System.out.println("A1线程获得B的锁等待C的锁");

synchronized(B){

System.out.println("A1线程获得B的锁也获得了C的锁");

}

}

}

}

线程A和A1处于相互等待对方释放锁,就这样一直僵持着,这段代码要避免死锁有两个方法:

方法一:在启动两个线程之间加入sleep()方法,只要休眠足够长的时间,让线程A先执行完再启动线程A1就可以避免死锁

t1.start();

try{

TimeUnit.SECONDS.sleep(5);

}catch(InterruptedException e){}

t2.start();

方法二,采用join()方法

t1.start();t1.join();

t2.start();t2.join();

在死锁案例中,最经典的教材案例应该是哲学家吃饭问题(我自己这样称谓),讲的是有5个哲学家,每个都做同样的事,一天的时间都在思考、吃饭的循环中度过,饭桌上一共只有5只筷子,一个人必须要有两只筷子才能够吃饭,所以哲学家就必须向身边的人拿筷子,当一起拿时就会产生死锁,假设哲学家都是先拿左边的筷子再拿右边的筷子,哲学家围成一圈,这样每个人左边和右边都有一只筷子。

例程:

package thread.test;

import java.util.concurrent.TimeUnit;

public class DeadLock{

public static void main(String[] args) {

try{

//一共有5双筷子5个哲学家

Chopsticks[] chops=new Chopsticks[5];

Philosopher[] peps=new Philosopher[5];

for(int i=0;i<5;i++){

chops[i] = new Chopsticks(i);

}

for(int i=0;i<5;i++){

peps[i] = new Philosopher(chops[i],chops[(i+1)/5],i);

}

//启动五个线程

for(int i=0;i<5;i++){

new Thread(peps[i]).start();

}

TimeUnit.SECONDS.sleep(4);//延时足够长的时间观察哲学家的动态

System.exit(0);  //程序必须退出!

}catch(InterruptedException e){

System.out.println("interrupted");

}

}

}

class Chopsticks {

private final int id;

public Chopsticks(int i){

id=i;

}

private boolean OnTake=false;

synchronized public void take()throws InterruptedException{

while(OnTake)

wait();

OnTake=true;

}

synchronized public void drop(){

OnTake=false;

notifyAll();

}

public String toString(){

return "chopstcks:"+id;

}

}

class Philosopher implements Runnable{

private Chopsticks left;

private Chopsticks right;

private final int id;

public Philosopher(Chopsticks l,Chopsticks r,int id){

left = l;     //左边的筷子

right = r;    //右边的筷子

this.id=id;

}

//模拟哲学家思考所占有的时间

private void pause()throws InterruptedException{

TimeUnit.MILLISECONDS.sleep(400);

}

@Override

public void run() {

try{

while(!Thread.interrupted()){

System.out.println(this+"在思考");

pause();

System.out.println(this+"拿筷子吃饭");

left.take();   //先拿左边的筷子

pause();

right.take();   //这顿饭永远也吃不完

System.out.println(this+"吃完放下筷子");

left.drop();

right.drop();

}

}catch(InterruptedException e){

e.printStackTrace();

}

}

public String toString(){

return " 哲学家"+id;

}

}

这段程序按照我们预想的结果是每个哲学家都在思考,然后依次吃完饭后放下筷子让另外的人吃,因为每个人需要两根筷子,总有人会没有筷子!但实际情况是:每个人都先拿起左边的筷子,然后等待某一个人把右边的筷子给他,但每个人都是这样想的,所以每个人都拿不到对方的筷子,所以这顿饭永远也吃不完!程序陷入了死锁。话说到这里,我们就有必要知道满足死锁的四个必要条件:1:互斥条件:资源每次只能被一个线程使用。如前面的“线程同步代码段”。 2:请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。 3:不剥夺条件:进程已获得的资源,在未使用完之前,无法强行剥夺。 4:循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件只要一个不满足就不会陷入死锁,一般我们容易破坏第四个条件,只要让其中一个哲学家先拿右边的筷子程序就不会陷入死锁!

修改后不会陷入死锁的程序:

for(int i=0;i<4;i++){  peps[i] = new Philosopher(chops[i],chops[i+1],i);  }  //最后一个哲学家先拿右边的筷子,即chops[0]筷子  peps[4] = new Philosopher(chops[0],chops[4],4); 看完这篇文章就留下了一个我从书上摘抄下来的练习:修改程序,使得当哲学家用完筷子之后,把筷子放在一个筷笼中,当哲学家要就餐时就从筷笼里取出下两根可用的筷子。这消除了死锁的可能吗?你能通过仅仅减少可用的筷子数目就重新引入死锁吗?如果要索要答案,就请留下邮箱地址,我发答案给你,或许你的答案比我的好得多!交流是一种快乐!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值