一生產者多消費者 --- 操作棧問題

一生產者多消費者 — 操作棧問題

本文是想通過棧的方式來進行線程間通訊。

1. 異常情況

看如下代碼:

package entity;

import java.util.ArrayList;
import java.util.List;

public class MyStack {
   private List list = new ArrayList();

   synchronized public void push() {
      try {
         if (list.size() == 1) {
            this.wait();
         }
         list.add("anyString=" + Math.random());
         this.notify();
         System.out.println("push=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   synchronized public String pop() {
      String returnValue = "";
      try {
         if (list.size() == 0) {
            this.wait();
         }
         returnValue = "" + list.get(0);
         System.out.println("pop操作中的:"
               + Thread.currentThread().getName() + " 線程呈wait狀態");
         list.remove(0);
         this.notify();
         System.out.println("pop=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return returnValue;
   }
}
package extthread;

import entity.MyStack;

public class Consumer_Thread extends Thread {

   private MyStack myStack;

   public Consumer_Thread(MyStack r) {
      this.myStack = r;
   }

   @Override
   public void run() {
      while (true) {
         myStack.pop();
      }
   }

}
package extthread;

import entity.MyStack;

public class Produce_Thread extends Thread {

   private MyStack myStack;

   public Produce_Thread(MyStack p) {
      this.myStack = p;
   }

   @Override
   public void run() {
      while (true) {
         myStack.push();
      }
   }

}

主函數如下:

package test.run;

import entity.MyStack;
import extthread.Consumer_Thread;
import extthread.Produce_Thread;

public class Run {
   public static void main(String[] args) throws Exception{
      MyStack myStack = new MyStack();

      Produce_Thread pThread = new Produce_Thread(myStack);
      pThread.start();

      Consumer_Thread cThread1 = new Consumer_Thread(myStack);
      Consumer_Thread cThread2 = new Consumer_Thread(myStack);
      cThread1.start();
      cThread2.start();
   }

}

運行如上代碼,會出現如下異常:

push=1
pop操作中的:Thread-2 線程呈wait狀態
pop=0
push=1
pop操作中的:Thread-2 線程呈wait狀態
pop=0
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:653)
	at java.util.ArrayList.get(ArrayList.java:429)
	at entity.MyStack.pop(MyStack.java:28)
	at extthread.Consumer_Thread.run(Consumer_Thread.java:16)

究其原因,是由於在Mystack類中,對戰的棧的操作時用了if來判斷:

synchronized public String pop() {
      String returnValue = "";
      try {
         if (list.size() == 0) {
            this.wait();
         }
         returnValue = "" + list.get(0);
         System.out.println("pop操作中的:"
               + Thread.currentThread().getName() + " 線程呈wait狀態");
         list.remove(0);
         this.notify();
         System.out.println("pop=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return returnValue;
   }

我們知道this.norify()操作會隨機喚醒一個在當前鎖對象上正在等待的線程,假如當前list.size==0爲true,這時候有一個consumer線程consumer1調用了this.wait(),此時consumer1線程釋放鎖,當前線程的執行流程暫停在了this.wait()這裏。 produce線程做了一次push操作,然後喚醒了consumer2線程,consumer2執行了remove操作之後,調用了this.norify(),此時把consumer1線程喚醒,consumer1接着上次的位置繼續執行,執行list.remove()操作,由於此時list裏面爲空,所以拋IndexOutOfBoundsException異常。

怎麼解決這個問題呢?想要解決這個問題,只需要把if判斷,改成while循環即可。

package entity;

import java.util.ArrayList;
import java.util.List;

public class MyStack {
   private List list = new ArrayList();

   synchronized public void push() {
      try {
          /**修改這裏爲while循環*/
         while (list.size() == 1) {
            this.wait();
         }
         list.add("anyString=" + Math.random());
         this.notify();
         System.out.println("push=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   synchronized public String pop() {
      String returnValue = "";
      try {
          /**修改這裏爲while循環*/
         while (list.size() == 0) {
            this.wait();
         }
         returnValue = "" + list.get(0);
         System.out.println("pop操作中的:"
               + Thread.currentThread().getName() + " 線程呈wait狀態");
         list.remove(0);
         this.notify();
         System.out.println("pop=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return returnValue;
   }
}

但是這種情況下。異常問題解決了,又會迎來假死的情況,下面來探討假死情況

2.假死情況

假死產生的原因就是所以的線程都執行了this.wait(),這種情況也很好理解。例如,程序運行,consumer1線程剛調用了this.wait()方法,produce線程往棧裏添加了一個元素,這時候produce執行notify操作,喚醒了consumer2,consumer2執行了remove之後,調用notify喚醒的是consumer1,而consumer1接着this.wait()的代碼繼續執行,又進入了while循環,再次調用了this.wait(),而consumer2也由於外層while循環,重新調用pop操作,也進入了wait狀態。此時所有的線程都在wait,整個程序進入了假死。

解決這個問題的方法就是把this.notify()改成this.nofify()。 程序運行便不會在出現假死,並且可以正常運行下去:

package entity;

import java.util.ArrayList;
import java.util.List;

public class MyStack {
   private List list = new ArrayList();

   synchronized public void push() {
      try {
          /**修改這裏爲while循環*/
         while (list.size() == 1) {
            this.wait();
         }
         list.add("anyString=" + Math.random());
         this.notify();
         System.out.println("push=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   synchronized public String pop() {
      String returnValue = "";
      try {
          /**修改這裏爲while循環*/
         while (list.size() == 0) {
            this.wait();
         }
         returnValue = "" + list.get(0);
         System.out.println("pop操作中的:"
               + Thread.currentThread().getName() + " 線程呈wait狀態");
         list.remove(0);
         this.notifyAll();
         System.out.println("pop=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return returnValue;
   }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章