今天搞了一下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); 看完这篇文章就留下了一个我从书上摘抄下来的练习:修改程序,使得当哲学家用完筷子之后,把筷子放在一个筷笼中,当哲学家要就餐时就从筷笼里取出下两根可用的筷子。这消除了死锁的可能吗?你能通过仅仅减少可用的筷子数目就重新引入死锁吗?如果要索要答案,就请留下邮箱地址,我发答案给你,或许你的答案比我的好得多!交流是一种快乐!