Java多线程5—死锁和wait()、notify()、notifyAll()

来源:互联网 发布:拼多多和淘宝哪个好 编辑:程序博客网 时间:2024/05/20 11:27
 在计算机领域有一个很经典的问题——哲学家进餐问题。在一个桌子旁边,围坐着五个哲学家,每个哲学家左右手边各有一支筷子。要求每个哲学家必须同时拿起 两支筷子才可以吃饭。开始吃饭的时候每个哲学家都去拿身边的筷子,这样每个哲学家的手里就只有一支筷子,哲学谁也不想先将筷子放下,都希望别的哲学家先放 下筷子。这样每个哲学家都吃不到饭。

         上面的问题同样也会在计算机中线程调度的时候发生。假设在程序中有两个线程1和2。每个线程分别有一个监视器A和B。线程1锁住了对象A的监视器,等待对 象B的监视器,线程2锁住了对象B的监视器,等待对象A的监视器,这样两个线程就都无法运行,就造成了死锁。

         我们不希望发生死锁的问题,下面我们举一个例子:有一个情报员需要将数据放到信箱,然后由另一个情报员去取情报。而且每个情报员在放或取情报的时候,先将 信箱占住,这时候另一个情报员无法对信箱操作。当然如果是先放情报,再取情报,不会发生错误。但是如果没有情报的时候,一个情报员先去取情报了,并且把信 箱占住,另一个情报员无法放情报,而第一情报员也无法释放信箱,这样两个情报员就无法工作,就造成了死锁。我们不希望发生这种情况,我们希望当情报员放了 情报通知另一情报员,当情报员取了情报后通知另一个情报员继续放情报。这时候我们就可以在Java中用wait()、notify()方法模拟上面的例 子。下面我们给出代码:

view plain
  1. public class Test  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         Queue q = new Queue();// 创建一个信箱  
  6.         Producer p = new Producer(q);// 创建一个放情报线程,需要一个信箱的对象作为参数  
  7.         Consumer c = new Consumer(q);// 创建一个取情报线程,需要一个信箱的对象作为参数  
  8.         p.start();// 启动线程  
  9.         c.start();// 启动线程  
  10.     }  
  11. }  
  12. class Producer extends Thread  
  13. {  
  14.     Queue q;  
  15.     Producer(Queue q)  
  16.     {  
  17.         this.q = q;  
  18.     }  
  19.     public void run()  
  20.     {  
  21.         for (int i = 0; i < 10; i++)  
  22.         {  
  23.             q.put(i);  
  24.               
  25.         }  
  26.     }  
  27. }  
  28.   
  29. class Consumer extends Thread// 我们设一次只放一个数据  
  30. {  
  31.     Queue q;  
  32.     Consumer(Queue q)  
  33.     {  
  34.         this.q = q;  
  35.     }  
  36.     public void run()  
  37.     {  
  38.         while (true)  
  39.         {  
  40.              q.get();  
  41.         }  
  42.     }  
  43. }  
  44. class Queue  
  45. {  
  46.     int value;  
  47.     boolean bFull = false;// 设一个Boolean变量标志信箱中是否有情报  
  48.     public synchronized void put(int i)  
  49.     {// 这里需要用同步的方法否则可能放到一半中断  
  50.         if (bFull == false)  
  51.         {// 如果信箱中没有情报放情报并将bFull设置成true,然后用notify()方法通知另一个情报员线程取情报  
  52.             value = i;  
  53.             bFull = true;  
  54.             System.out.println("Producer put " + i);//这里是修改的  
  55.             notify();  
  56.         }  
  57.         try  
  58.         {// 可能开始已经有情报,这时候执行wait()方法等待  
  59.             wait();// 该方法会跑出异常需要捕获  
  60.         } catch (Exception e)  
  61.         {  
  62.             e.printStackTrace();  
  63.         }  
  64.           
  65.     }  
  66.     public synchronized int get()  
  67.     {// 这里需要用同步的方法否则可能取到一半中断  
  68.         if (bFull == false)  
  69.         {// 如果没有情报调用wait()方法等待  
  70.             try  
  71.             {  
  72.                 wait();// 该方法会跑出异常需要捕获  
  73.             }  
  74.             catch (Exception e)  
  75.             {  
  76.                 e.printStackTrace();  
  77.             }  
  78.         }  
  79.         bFull = false;// 如果有情报,将bFull设置成false,并用notify()方法通知另外一个情报员线程,然后返回情报数据  
  80.         System.out.println("Consumer get " +value);//这里是修改的  
  81.         notify();  
  82.         return value;  
  83.     }  


         上面的程序注释详细的介绍了,这个程序运行时候注意问题。

          每一个对象除了有一个锁之外,还有一个等待队列(wait set),当一个对象刚创建的时候,它的对待队列是空的。我们应该在当前线程锁住对象的锁后,去调用该对象的wait方法。当调用对象的notify方法 时,将从该对象的等待队列中删除一个任意选择的线程,这个线程将再次成为可运行的线程。所以我们使用wait和notify方法的时候必须确定这两个线程 在同一个对象的等待队列。

         最后在说一句notifyAll()方法,在等待队列中会有许多的等待线程,调用该方法后,将会唤醒所有的线程。

0 0
原创粉丝点击