1. 程式人生 > >Java多執行緒-生產者和消費者

Java多執行緒-生產者和消費者

標籤(空格分隔): java thread

簡介:
隨著作業系統的不斷更新迭代,多執行緒程式設計已經變的十分常見,java虛擬機器的多執行緒一般也是建立在作業系統本地native執行緒之上的,從而不必自己管理執行緒間的切換,直接交由本機作業系統進行排程和管理。對於java程式來說,它是執行在虛擬機器上的,由於執行緒間很多資源都是共享的,比如全域性資料等等,因此,執行緒間的同步也就不是那麼複雜的。java也提供了許多實現執行緒同步的方法,我們這裡主要分析wait和notify。

問題:
在一個盒子裡,能放置一定數量的物體A,同一時間只能准許一個人往箱子裡放物體或者一個人從盒子中取出物品。當盒子滿了,再往裡面放物體的人需要等待。當盒子空了,從盒子中取出物品的人需要等待。
這就是一個典型的生產者和消費者模型,下面在程式碼的基礎上模擬這個過程。
1. 盒子物件

public class DataManager {

  private static final int LIMIT = 20;
  private List<String> datas = new ArrayList<>();

  public synchronized void put( String data ) {
    while ( datas.size() == LIMIT ) {
      // size limit, need wait.
      try {
        // 處於wait等待的執行緒會暫時釋放鎖,當其被喚醒時,會重新獲得鎖
wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } // 執行緒被喚醒後,重新檢查條件 } datas.add( data ); // 喚醒所有等待的執行緒 notifyAll(); } public synchronized String get() { while ( datas.size() == 0 ) { // datas is empty, need wait. try
{ wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } String data = datas.get( 0 ); datas.remove( 0 ); notifyAll(); return data; } }

DataManger展示了盒子這個模型,這裡需要理解wait和notify以及notifyAll這幾個方法。

2.生產者模型

public class Producer implements Runnable {

  static int count = 0;
  private DataManager mDataManager;

  public Producer( DataManager dataManager ) {
    mDataManager = dataManager;
  }

  @Override
  public void run() {
    while ( true ) {
      try {
        sleep( 100 );
      } catch ( InterruptedException e ) {
        e.printStackTrace();
      }
      String data = "data - " + count++;
      mDataManager.put( data );
      System.out.println( "Producer " + Thread.currentThread().getName() + ", put -> " + data );
    }
  }
}

3.消費者模型

public class Consumer implements Runnable {

  private DataManager mDataManager;

  public Consumer( DataManager dataManager ) {
    mDataManager = dataManager;
  }

  @Override
  public void run() {
    while ( true ) {
      try {
        sleep( 100 );
      } catch ( InterruptedException e ) {
        e.printStackTrace();
      }
      String data = mDataManager.get();
      System.out.println( "Consumer " + Thread.currentThread().getName() + ", get -> " + data );
    }
  }
}

Client端程式碼

public class Client {

  public static void main( String[] args ) {
    DataManager dataManager = new DataManager();
    for ( int i = 0; i < 20; i++ ) {
      new Thread( new Producer( dataManager ) ).start();
      new Thread( new Consumer( dataManager ) ).start();
    }
  }

}

總結:
整體結構來說是比較簡單的,所有生產者Producer和所有消費者Consumer共享同一個盒子DataManager。同時對盒子進行存取操作。對於上述程式碼,應該重點理解wait、notify以及notifyAll它們作用的物件,它們三者必須在同步塊中使用,同時理解wait會釋放物件鎖,這點和sleep有區別,而被喚醒後又會重新持有物件鎖,這個是執行緒同步的關鍵,也是理解這些程式碼的關鍵。當然java還有許多api提供了執行緒同步的方法,比如鎖機制,其實兩者在原理上是大同小異,都是基於本機作業系統的執行緒同步機制。