生產者-消費者模式是一個經典的多線程設計模式。
在生產者-消費者模式中,一般有兩類線程,即若干個生產者和消費者線程。java
測試:設計模式
/** * 生產者與消費者案例。 * @author */ public class TestProductorAndConsumer { public static void main(String[] args) { //建立職員 Clerk clerk = new Clerk(); //建立生產者與消費者線程 Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); new Thread(productor, "生產者1").start(); new Thread(consumer, "消費者1").start(); new Thread(productor, "生產者2").start(); new Thread(consumer, "消費者2").start(); } } // Clerk職員 class Clerk { private int product = 0; //產品數量 private int capacity = 4; // 容量 // 進貨 public synchronized void get() { if (product >= capacity) { System.out.println("產品已滿!"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + " : " + ++product); this.notifyAll(); } } // 賣貨 public synchronized void sale() { if (product <= 0) { System.out.println("缺貨!"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + " : " + --product); this.notifyAll(); } } } // 生產者 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } clerk.get(); } } } // 消費者 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.sale(); } } }
這就是一個簡單的生產者與消費者模型。
咱們在Clerk類中給get()方法和sale()方法加了synchronized修飾符,來保證線程同步。緩存
可是運行後發現程序並無運行結束,分析發現,咱們的生產者線程最後沒有被喚醒,致使程序沒有結束。安全
/** * 對Clerk類的get()與sale()方法作一點修改 */ public synchronized void get() { if (product >= capacity) { System.out.println("產品已滿!"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " : " + ++product); this.notifyAll(); } // 賣貨 public synchronized void sale() { if (product <= 0) { System.out.println("缺貨!"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " : " + --product); this.notifyAll(); }
把notifyAll() 方法提到了else外面,保證每一個線程結束都能調用notifyAll()方法,運行一下,發現程序確實能結束,可是程序 product 變成了負數。這是因爲調用notifyAll()喚醒了消費者模型,執行–product致使。多線程
咱們來看一下wait()這個方法:ide
這就是咱們要解決的虛假喚醒問題!!!。
文檔提醒咱們使用循環。再對程序作一點修改測試
// 進貨 public synchronized void get() { // 使用while防止虛假喚醒 while(product >= capacity) { System.out.println("產品已滿!"); try { // 在一個參數版本中,中斷和虛假的喚醒是可能的,這個方法應該老是在循環中使用: this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 除非product<capacity,不然不執行++product操做 System.out.println(Thread.currentThread().getName() + " : " + ++product); this.notifyAll(); } // 賣貨 public synchronized void sale() { while (product <= 0) { System.out.println("缺貨!"); try { // 在一個參數版本中,中斷和虛假的喚醒是可能的,這個方法應該老是在循環中使用: this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 除非product>capacity,不然不執行--product操做 System.out.println(Thread.currentThread().getName() + " : " + --product); this.notifyAll(); }
將 if 改成 while 循環後,
生產者線程被喚醒後進行判斷:若是product >= capacity,則繼續調用wait()等待,直到再次被喚醒。若是product < capacity, 則執行++product。
同理消費者線程被喚醒後也會進行判斷,不知足條件會繼續等待,直到再次被喚醒。知足條件後處理任務。this
至此,咱們的生產者-消費者模型就圓滿完成了。spa
咱們再對程序作一點修改,不使用synchronized來修飾方法,而是採用可重入鎖ReentrantLock來手動加鎖與釋放鎖。此時咱們也就不能再使用wait()和notifyAll()方法了,由於這兩個方法synchronized關鍵字合做使用。
此處咱們須要使用Condition條件。線程
直接看代碼:
// 進貨 public void get() { lock.lock(); try { // 使用while防止虛假喚醒 while(product >= capacity) { System.out.println("產品已滿!"); try { // 在一個參數版本中,中斷和虛假的喚醒是可能的,這個方法應該老是在循環中使用: condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 除非product<capacity,不然不執行++product操做 System.out.println(Thread.currentThread().getName() + " : " + ++product); condition.signalAll(); }finally { lock.unlock(); } } // 賣貨 public void sale() { lock.lock(); try { while (product <= 0) { System.out.println("缺貨!"); try { // 在一個參數版本中,中斷和虛假的喚醒是可能的,這個方法應該老是在循環中使用: condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 除非product>capacity,不然不執行--product操做 System.out.println(Thread.currentThread().getName() + " : " + --product); condition.signalAll(); }finally { lock.unlock(); } }
咱們定義了一個可重入鎖 ReentrantLock lock, 經過lock.lock()來加鎖,經過lock.unlock()來釋放鎖,讓加鎖範圍更加靈活。
感謝你看到這裏,文章有什麼不足還請指正,以爲文章對你有幫助的話記得給我點個贊,天天都會分享java相關技術文章或行業資訊,歡迎你們關注和轉發文章!