死锁线程探讨Java中的死锁现象

时间:2023-12-18 15:12:38

题记:写这篇博客要主是加深自己对死锁线程的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。

今天搞了一下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);

看完这篇文章就留下了一个我从书上摘抄上去的练习:修改程序,使切当哲学家用完筷子以后,把筷子放在一个筷笼中,当哲学家要就餐时就从筷笼里取出下两根可用的筷子。这消除了死锁的可能吗?你能通过仅仅增加可用的筷子数目就从新引入死锁吗?如果要索要谜底,就请留下邮箱地址,我发谜底给你,或许你的谜底比我的好得多!交流是一种快乐!

文章结束给大家分享下程序员的一些笑话语录:

火车
一个年轻的程序员和一个项目经理登上了一列在山里行驶的火车,他们发现 列车上几乎都坐满了,只有两个在一起的空位,这个空位的对面是一个老奶 奶和一个年轻漂亮的姑娘。两个上前坐了下来。程序员和那个姑娘他们比较 暧昧地相互看对方。这时,火车进入山洞,车厢里一片漆黑。此时,只听见 一个亲嘴的声音,随后就听到一个响亮的巴掌声。很快火车出了山洞,他们 四个人都不说话。
那个老奶奶在喃喃道, “这个年轻小伙怎么这么无礼, 不过我很高兴我的孙女 扇了一个巴掌”。
项目经理在想,“没想到这个程序员居然这么大胆,敢去亲那姑娘,只可惜那 姑娘打错了人,居然给打了我。”
漂亮的姑娘想,“他亲了我真好,希望我的祖母没有打疼他”。
程序员坐在那里露出了笑容, “生活真好啊。 这一辈子能有几次机会可以在亲 一个美女的同时打项目经理一巴掌啊”

---------------------------------
原创文章 By
死锁和线程
---------------------------------