標籤:linkedblockingqueue 生產者 消費者
生產者消費者問題
(英語:Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多線程同步問題的經典案例。該問題描述了兩個共用固定大小緩衝區的線程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。生產者的主要作用是產生一定量的資料放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些資料。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入資料,消費者也不會在緩衝區中空時消耗資料。
要解決該問題,就必須讓生產者在緩衝區滿時休眠(要麼乾脆就放棄資料),等到下次消費者消耗緩衝區中的資料的時候,生產者才能被喚醒,開始往緩衝區添加資料。同樣,也可以讓消費者在緩衝區空時進入休眠,等到生產者往緩衝區添加資料之後,再喚醒消費者。通常採用處理序間通訊的方法解決該問題,常用的方法有號誌法[1]等。如果解決方案不夠完善,則容易出現死結的情況。出現死結時,兩個線程都會陷入休眠,等待對方喚醒自己。該問題也能被推廣到多個生產者和消費者的情形。
現實中的應用
比如一個飯店,它有一個廚師和一個服務員。這個服務員必須等待廚師準備好食物。當廚師準備好時,他會通知服務員,之後服務員上菜,然後返回繼續等待。這是一個任務協作的執行個體:廚師代表生產者,服務員代表消費者。兩個任務必須在食物被生產和消費時進行握手,而系統必須以有序的方式關閉。
可以用來表示這種關係。
生產者消費者的實現
這兒是用阻塞隊列LinkedBlockingQueue來實現的,阻塞隊列
package com.a.consumer;import java.util.concurrent.*;public class consumer3 { // 建立一個阻塞隊列 private LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<Object>(10); public consumer3() { } public void start() { new Producer().start(); new Consumer().start(); } public static void main(String[] args) throws Exception { consumer3 s3 = new consumer3(); s3.start(); } class Producer extends Thread { public void run() { while (true) { try { Object o = new Object(); // 取出一個對象 queue.put(o); //隊列滿時會自動阻塞 System.out.println("Producer: " + o); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread { public void run() { while (true) { try { // 取出一個對象 Object o = queue.take(); System.out.println("Consumer: " + o); } catch (InterruptedException e) { e.printStackTrace(); } } } }}
下面研究下LinkedBlockingQueue的源碼
首先看一下它的put方法
注意下面這句話,它會調用putLock.lockInterruptibly()這個方法,來試圖擷取這個putLock這個鎖
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. int c = -1; final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { /* * Note that count is used in wait guard even though it is * not protected by lock. This works because count can * only decrease at this point (all other puts are shut * out by lock), and we (or some other waiting put) are * signalled if it ever changes from * capacity. Similarly for all other uses of count in * other wait guards. */ while (count.get() == capacity) { notFull.await(); } enqueue(e); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); }
在看lockInterruptibly()方法的源碼,實際是調用的sync這個同步器的acquireInterruptibly這個方法
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
在看acquireInterruptibly這個方法,就是先檢查當前線程中斷標識位是不是true,是true時,將拋出中斷異常,否則會試著去擷取鎖,當沒有獲得鎖時,會執行doAcquireInterruptibly這個方法
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
下面在看一下doAcquireInterruptibly這個方法,要注意shouldParkAfterFailedAcquire這個方法,就是當它為true時,會接著執行parkAndCheckInterrupt()這個方法,當它也為真時,會跳出當前迴圈,然後取消擷取鎖,並且同時拋出異常。
private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) break; } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } // Arrive here only if interrupted cancelAcquire(node); throw new InterruptedException(); }
java消費者生產者模式及JDK之阻塞隊列LinkedBlockingQueue實現