前言
這篇博客南國主要講解關於Java中阻塞隊列的知識點,提到阻塞隊列(BlockingQueue)想必大家最先想到的是生產者-消費者,誠然這也是阻塞隊列最直接的應用場景。 本篇分爲四個章節,BlockingQueue簡介,常見的基本操作,常用的BlockingQueue實現類和應用demo。這裏針對BlockingQueue的應用南國主要寫了生產者-消費者的實現和線程通信的實現。前三個部分的基礎知識總結,很多內容參考了併發容器之BlockingQueue的敘述,結合自己的理解南國 在一些內容上做了增加和重新編輯。
話不多說,乾貨送上~
1. BlockingQueue簡介
在實際編程中,會經常使用到JDK中Collection集合框架中的各種容器類如實現List,Map,Queue接口的容器類,但是這些容器類基本上不是線程安全的,除了使用Collections可以將其轉換爲線程安全的容器,Doug Lea大師爲我們都準備了對應的線程安全的容器,如實現List接口的CopyOnWriteArrayList,實現Map接口的ConcurrentHashMap,實現Queue接口的ConcurrentLinkedQueue。
在我們學習操作系統時遇到的一個最經典的"生產者-消費者"問題中,隊列通常被視作線程間操作的數據容器,這樣,可以對各個模塊的業務功能進行解耦,生產者將“生產”出來的數據放置在數據容器中,而消費者僅僅只需要在“數據容器”中進行獲取數據即可,這樣生產者線程和消費者線程就能夠進行解耦,只專注於自己的業務功能即可。阻塞隊列(BlockingQueue)被廣泛使用在“生產者-消費者”問題中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。當隊列容器已滿,生產者線程會被阻塞,直到隊列未滿;當隊列容器爲空時,消費者線程會被阻塞,直至隊列非空時爲止。
2. 常見的基本操作
BlockingQueue基本操作總結如下:
BlockingQueue繼承於Queue接口,因此,對數據元素的基本操作有:
插入元素:
add(E e) :往隊列插入數據,當隊列滿時,插入元素時會拋出IllegalStateException異常;
offer(E e):當往隊列插入數據時,插入成功返回true,否則則返回false。當隊列滿時不會拋出異常;
刪除元素:
remove(Object o):從隊列中刪除數據,成功則返回true,否則爲false
poll:刪除數據,當隊列爲空時,返回null;
查看元素:
element:獲取隊頭元素,如果隊列爲空時則拋出NoSuchElementException異常;
peek:獲取隊頭元素,如果隊列爲空則拋出NoSuchElementException異常
接下來,南國講一下BlockingQueue具有的特殊操作:
插入數據:
put:當阻塞隊列容量已經滿時,往阻塞隊列插入數據的線程會被阻塞,直至阻塞隊列已經有空餘的容量可供使用;
offer(E e, long timeout, TimeUnit unit):若阻塞隊列已經滿時,同樣會阻塞插入數據的線程,直至阻塞隊列已經有空餘的地方,與put方法不同的是,該方法會有一個超時時間,若超過當前給定的超時時間,插入數據的線程會退出;
刪除數據:
take():當阻塞隊列爲空時,獲取隊頭數據的線程會被阻塞;
poll(long timeout, TimeUnit unit):當阻塞隊列爲空時,獲取數據的線程會被阻塞,另外,如果被阻塞的線程超過了給定的時長,該線程會退出
3. 常用的BlockingQueue
實現BlockingQueue接口的有ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue,而這幾種常見的阻塞隊列也是在實際編程中會常用的,下面對這幾種常見的阻塞隊列進行說明:
3.1. ArrayBlockingQueue
ArrayBlockingQueue是由數組實現的有界阻塞隊列。該隊列命令元素FIFO(先進先出)。因此,對頭元素head是隊列中存在時間最長的數據元素,而對尾數據tail則是當前隊列最新的數據元素。ArrayBlockingQueue可作爲“有界數據緩衝區”,生產者插入數據到隊列容器中,並由消費者提取。ArrayBlockingQueue一旦創建,容量不能改變。
當隊列容量滿時,嘗試將元素放入隊列將導致操作阻塞;嘗試從一個空隊列中取一個元素也會同樣阻塞。
ArrayBlockingQueue默認情況下不能保證線程訪問隊列的公平性,所謂公平性是指嚴格按照線程等待的絕對時間順序,即最先等待的線程能夠最先訪問到ArrayBlockingQueue。而非公平性則是指訪問ArrayBlockingQueue的順序不是遵守嚴格的時間順序,有可能存在,一旦ArrayBlockingQueue可以被訪問時,長時間阻塞的線程依然無法訪問到ArrayBlockingQueue。如果保證公平性,通常會降低吞吐量。如果需要獲得公平性的ArrayBlockingQueue,可採用如下代碼:
private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);
3.1.1 ArrayBlockingQueue的主要屬性
ArrayBlockingQueue的主要屬性如下:
/** The queued items */
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
從源碼中可以看出ArrayBlockingQueue內部是採用數組進行數據存儲的(屬性items),爲了保證線程安全,採用的是ReentrantLock lock,爲了保證可阻塞式的插入刪除數據利用的是Condition,當獲取數據的消費者線程被阻塞時會將該線程放置到notEmpty等待隊列中,當插入數據的生產者線程被阻塞時,會將該線程放置到notFull等待隊列中。而notEmpty和notFull等中要屬性在構造方法中進行創建:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
接下來,主要看看可阻塞式的put和take方法是怎樣實現的。
3.1.2 put方法詳解
put(E e)方法源碼如下:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果當前隊列已滿,將線程移入到notFull等待隊列中
while (count == items.length)
notFull.await();
//滿足插入數據的要求,直接進行入隊操作
enqueue(e);
} finally {
lock.unlock();
}
}
該方法的邏輯很簡單,當隊列已滿時(count == items.length)將線程移入到notFull等待隊列中,如果當前滿足插入數據的條件,就可以直接調用 enqueue(e)插入數據元素。enqueue方法源碼爲:
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
//插入數據
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//通知消費者線程,當前隊列中有數據可供消費
notEmpty.signal();
}
enqueue方法的邏輯同樣也很簡單,先完成插入數據,即往數組中添加數據(items[putIndex] = x),然後通知被阻塞的消費者線程,當前隊列中有數據可供消費(notEmpty.signal())。
3.1.3 take方法詳解
take方法源碼如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果隊列爲空,沒有數據,將消費者線程移入等待隊列中
while (count == 0)
notEmpty.await();
//獲取數據
return dequeue();
} finally {
lock.unlock();
}
}
take方法也主要做了兩步:1. 如果當前隊列爲空的話,則將獲取數據的消費者線程移入到等待隊列中;2. 若隊列不爲空則獲取數據,即完成出隊操作dequeue。dequeue方法源碼爲:
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//獲取數據
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
//通知被阻塞的生產者線程
notFull.signal();
return x;
}
dequeue方法也主要做了兩件事情:1. 獲取隊列中的數據,即獲取數組中的數據元素((E) items[takeIndex]);2. 通知notFull等待隊列中的線程,使其由等待隊列移入到同步隊列中,使其能夠有機會獲得lock,並執行完成功退出。
從以上分析,可以看出put和take方法主要是通過condition的通知機制來完成可阻塞式的插入數據和獲取數據。在理解ArrayBlockingQueue後再去理解LinkedBlockingQueue就很容易了。
3.2. LinkedBlockingQueue
LinkedBlockingQueue是用鏈表實現的阻塞隊列,同樣滿足FIFO的特性。與ArrayBlockingQueue相比起來具有更高的吞吐量,爲了防止LinkedBlockingQueue容量迅速增,損耗大量內,通常在創建LinkedBlockingQueue對象時,會指定其大小(如果指定了大小,我們判定它爲有界隊列),如果未指定,容量等於Integer.MAX_VALUE(我們視它爲無界隊列)。查看它的構造方法:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
3.2.1 LinkedBlockingQueue的主要屬性
LinkedBlockingQueue的主要屬性有:
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.
* Invariant: head.item == null
*/
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next == null
*/
private transient Node<E> last;
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
可以看出與ArrayBlockingQueue主要的區別是,LinkedBlockingQueue在插入數據和刪除數據時分別是由兩個不同的lock(takeLock和putLock)來控制線程安全的,因此,也由這兩個lock生成了兩個對應的condition(notEmpty和notFull)來實現可阻塞的插入和刪除數據。並且,採用了鏈表的數據結構來實現隊列,Node結點的定義爲:
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
接下來,我們也同樣來看看put方法和take方法的實現。
3.2.2 put方法詳解
put方法源碼爲:
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;
Node<E> node = new Node<E>(e);
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(node);
c = count.getAndIncrement();
//若隊列滿足插入數據的條件,則通知被阻塞的生產者線程
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
put方法的邏輯也同樣很容易理解,可見註釋。基本上和ArrayBlockingQueue的put方法一樣。
3.2.3 take方法的源碼如下:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
//當前隊列爲空,則阻塞當前線程,將其移入到等待隊列中,直至滿足條件
while (count.get() == 0) {
notEmpty.await();
}
//移除隊頭元素,獲取數據
x = dequeue();
c = count.getAndDecrement();
//如果當前滿足移除元素的條件,則通知被阻塞的消費者線程
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
take方法的主要邏輯請見於註釋,也很容易理解。
3.2.4. ArrayBlockingQueue與LinkedBlockingQueue的比較(重要)
相同點:ArrayBlockingQueue和LinkedBlockingQueue都是通過condition通知機制來實現可阻塞式插入和刪除元素,並滿足線程安全的特性;
不同點:1. ArrayBlockingQueue底層是採用的數組進行實現,而LinkedBlockingQueue則是採用鏈表數據結構; 2. ArrayBlockingQueue插入和刪除數據,只採用了一個lock,而LinkedBlockingQueue則是在插入和刪除分別採用了putLock和takeLock,這樣可以降低線程由於線程無法獲取到lock而進入WAITING狀態的可能性,從而提高了線程併發執行的效率。
3.3. PriorityBlockingQueue
PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認情況下元素採用自然順序進行排序,也可以通過自定義類實現compareTo()方法來指定元素排序規則,或者初始化時通過構造器參數Comparator來指定排序規則。
3.4. SynchronousQueue
它的實質是一種無緩衝的等待隊列。SynchronousQueue每個插入操作必須等待另一個線程進行相應的刪除操作,因此,SynchronousQueue實際上沒有存儲任何數據元素,因爲只有線程在刪除數據時,其他線程才能插入數據,同樣的,如果當前有線程在插入數據時,線程才能刪除數據。SynchronousQueue也可以通過構造器參數來爲其指定公平性。
- 公平模式:SynchronousQueue採用公平鎖,並配合一個FIFO隊列來阻塞多餘的生產者和消費者。
- 非公平模式:SynchronoueQueue採用非公平鎖,同時配合一個LIFO隊列來管理多餘的生產者和消費者。
3.5. LinkedTransferQueue
LinkedTransferQueue是一個由鏈表數據結構構成的無界阻塞隊列,由於該隊列實現了TransferQueue接口,與其他阻塞隊列相比主要有以下不同的方法:
- transfer(E e) 如果當前有線程(消費者)正在調用take()方法或者可延時的poll()方法進行消費數據時,生產者線程可以調用transfer方法將數據傳遞給消費者線程。如果當前沒有消費者線程的話,生產者線程就會將數據插入到隊尾,直到有消費者能夠進行消費才能退出;
- tryTransfer(E e) tryTransfer方法如果當前有消費者線程(調用take方法或者具有超時特性的poll方法)正在消費數據的話,該方法可以將數據立即傳送給消費者線程,如果當前沒有消費者線程消費數據的話,就立即返回false。因此,與transfer方法相比,transfer方法是必須等到有消費者線程消費數據時,生產者線程才能夠返回。而tryTransfer方法能夠立即返回結果退出。
- tryTransfer(E e,long timeout,imeUnit unit)
與transfer基本功能一樣,只是增加了超時特性,如果數據才規定的超時時間內沒有消費者進行消費的話,就返回false。
3.6. LinkedBlockingDeque
LinkedBlockingDeque是基於鏈表數據結構的有界阻塞雙端隊列,如果在創建對象時爲指定大小時,其默認大小爲Integer.MAX_VALUE。與LinkedBlockingQueue相比,主要的不同點在於,LinkedBlockingDeque具有雙端隊列的特性。LinkedBlockingDeque基本操作如下圖所示:
如上圖所示,LinkedBlockingDeque的基本操作可以分爲四種類型:1.特殊情況,拋出異常;2.特殊情況,返回特殊值如null或者false;3.當線程不滿足操作條件時,線程會被阻塞直至條件滿足;4. 操作具有超時特性。
另外,LinkedBlockingDeque實現了BlockingDueue接口而LinkedBlockingQueue實現的是BlockingQueue,這兩個接口的主要區別如下圖所示:
從上圖可以看出,兩個接口的功能是可以等價使用的,比如BlockingQueue的add方法和BlockingDeque的addLast方法的功能是一樣的。
3.7. DelayQueue
DelayQueue是一個存放實現Delayed接口的數據的無界阻塞隊列,只有當數據對象的延時時間達到時才能插入到隊列進行存儲。如果當前所有的數據都還沒有達到創建時所指定的延時期,則隊列沒有隊頭,並且線程通過poll等方法獲取數據元素則返回null。所謂數據延時期滿時,則是通過Delayed接口的getDelay(TimeUnit.NANOSECONDS)來進行判定,如果該方法返回的是小於等於0則說明該數據元素的延時期已滿。
4. 應用Demo
通過前面的學習,相比你已經對阻塞隊列以及常用的類型有了一個基本的瞭解。 下面,南國將兩個阻塞應用的最廣泛的例子:生產者-消費者模式的實現,實現線程通信
4.1 實現生產者-消費者模式
1. 拋開這篇博客提到的阻塞隊列,我們手動寫一個非阻塞隊列的方式實現消費者-生產者模式。
package 併發多線程.生產者_消費者模式;
import java.util.PriorityQueue;
/**
* 使用Object.wait()和Object.notify() 非阻塞隊列的方式實現消費者-生產者模式
*
* @author xjh 2019.12.26
*/
public class Wait_Notify {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);
public static void main(String[] args) {
Wait_Notify wait_notify = new Wait_Notify();
Producer producer =wait_notify.new Producer(); //內部類的對象創建,需要通過外部類對象進行調用
Consumer consumer=wait_notify.new Consumer();
producer.start();
consumer.start();
}
//創建內部類 Producer表示生產者線程相關的類
class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
while (true) {
synchronized (queue) {
//對代碼塊進行加鎖
while (queue.size() == queueSize) { //隊列已滿,不能再生產了
System.out.println("the queue is full, please wait...");
try {
queue.wait(); //當前線程掛起,進入等待隊列
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify(); //notify 喚醒等待掛起的線程
}
}
queue.offer(1); //入隊一個元素
queue.notify();
System.out.println("I have inserted one element, the rest capacity is: " + (queueSize - queue.size()));
}
}
}
}
//創建內部類 Consumer表示消費者線程相關的類
class Consumer extends Thread {
@Override
public void run() {
consume();
}
private void consume() {
while (true) {
synchronized (queue) {
//對代碼塊進行加鎖
while (queue.size() == 0) { //隊列爲空,不能再消費了
System.out.println("the queue is empty, please wait...");
try {
queue.wait(); //當前線程掛起,進入等待隊列
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify(); //notify 喚醒等待掛起的線程
}
}
queue.poll(); //出隊一個元素
queue.notify();
System.out.println("I have polled one element, the rest elements are: " + queue.size());
}
}
}
}
}
輸出結果:
......
the queue is full, please wait...
I have polled one element, the rest elements are: 9
I have polled one element, the rest elements are: 8
I have polled one element, the rest elements are: 7
I have polled one element, the rest elements are: 6
I have polled one element, the rest elements are: 5
I have polled one element, the rest elements are: 4
I have polled one element, the rest elements are: 3
I have polled one element, the rest elements are: 2
I have polled one element, the rest elements are: 1
I have polled one element, the rest elements are: 0
the queue is empty, please wait...
I have inserted one element, the rest capacity is: 9
I have inserted one element, the rest capacity is: 8
I have inserted one element, the rest capacity is: 7
I have inserted one element, the rest capacity is: 6
I have inserted one element, the rest capacity is: 5
I have inserted one element, the rest capacity is: 4
I have inserted one element, the rest capacity is: 3
I have inserted one element, the rest capacity is: 2
I have inserted one element, the rest capacity is: 1
I have inserted one element, the rest capacity is: 0
.......
2. 使用阻塞隊列實現生產者-消費者(這裏我使用的是ArrayBlockingQueue)
package 併發多線程.生產者_消費者模式;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 使用ArrayBlockQueue實現生產者消費者模式
* @author xjh 2019.12.26
*/
public class ArrayBlockQueue_Demo {
private int queueSize = 10;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(queueSize);
// 非阻塞模式用的PriorityQueue,阻塞模式下我們使用ArrayBlockingQueue
public static void main(String[] args) {
ArrayBlockQueue_Demo arrayBlockQueue_demo = new ArrayBlockQueue_Demo();
Producer producer=arrayBlockQueue_demo.new Producer();
Consumer consumer=arrayBlockQueue_demo.new Consumer();
consumer.start();
producer.start();
}
//創建內部類 Producer表示生產者線程相關的類
class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
while (true) {
try {
queue.put(1);
System.out.println("I have inserted one element, the rest capacity is: " + (queueSize - queue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//創建內部類 Consumer表示消費者線程相關的類
class Consumer extends Thread {
@Override
public void run() {
consume();
}
private void consume() {
while (true) {
try {
queue.take();
System.out.println("I have took one element, the rest elements are: " + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
輸出結果:
.........
I have took one element, the rest elements are: 9
I have took one element, the rest elements are: 8
I have took one element, the rest elements are: 8
I have took one element, the rest elements are: 7
I have took one element, the rest elements are: 6
I have took one element, the rest elements are: 5
I have took one element, the rest elements are: 4
I have took one element, the rest elements are: 3
I have took one element, the rest elements are: 2
I have took one element, the rest elements are: 1
I have took one element, the rest elements are: 0
I have inserted one element, the rest capacity is: 1
I have inserted one element, the rest capacity is: 9
I have inserted one element, the rest capacity is: 9
I have inserted one element, the rest capacity is: 8
I have inserted one element, the rest capacity is: 7
I have inserted one element, the rest capacity is: 6
I have inserted one element, the rest capacity is: 5
I have inserted one element, the rest capacity is: 4
I have inserted one element, the rest capacity is: 3
I have inserted one element, the rest capacity is: 2
I have inserted one element, the rest capacity is: 1
I have inserted one element, the rest capacity is: 0
.........
注意,這兩段代碼的結果都是截取的部分效果。 讀者相比發現了使用阻塞隊列代碼要簡單得多,不需要再單獨考慮同步和線程間通信的問題。
在併發編程中,一般推薦使用阻塞隊列,這樣實現可以儘量地避免程序出現意外的錯誤。
阻塞隊列使用最經典的場景就是socket客戶端數據的讀取和解析,讀取數據的線程不斷將數據放入隊列,然後解析線程不斷從隊列取數據解析。還有其他類似的場景,只要符合生產者-消費者模型的都可以使用阻塞隊列。
4.2 阻塞隊列實現線程通信(這裏我使用的是LinkedBlockingQueue)
package 併發多線程;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 使用BlockingQueue來實現線程通信
* @author xjh 2019.09.09
* 這裏我用了兩種玩法:
一種是共享一個queue,根據peek和poll的不同來實現;
第二種是兩個queue,利用take()會自動阻塞來實現。
*/
class MethodSeven {
//1.共享一個queue,根據peek和poll的不同來實現;
private final LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
public Runnable newThreadOne() {
final String[] inputArr = Helper.buildNoArr(52);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
for (int i = 0; i < arr.length; i = i + 2) {
Helper.print(arr[i], arr[i + 1]);
queue.offer("TwoToGo");
while (!"OneToGo".equals(queue.peek())) {
}
queue.poll();
}
}
};
}
public Runnable newThreadTwo() {
final String[] inputArr = Helper.buildCharArr(26);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
for (int i = 0; i < arr.length; i++) {
while (!"TwoToGo".equals(queue.peek())) {
}
queue.poll();
Helper.print(arr[i]);
queue.offer("OneToGo");
}
}
};
}
//2.兩個queue,利用take()會自動阻塞來實現。
private final LinkedBlockingQueue<String> queue1 = new LinkedBlockingQueue<>();
private final LinkedBlockingQueue<String> queue2 = new LinkedBlockingQueue<>();
public Runnable newThreadThree() {
final String[] inputArr = Helper.buildNoArr(52);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
for (int i = 0; i < arr.length; i = i + 2) {
Helper.print(arr[i], arr[i + 1]);
try {
queue2.put("TwoToGo");
queue1.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
public Runnable newThreadFour() {
final String[] inputArr = Helper.buildCharArr(26);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
for (int i = 0; i < arr.length; i++) {
try {
queue2.take();
Helper.print(arr[i]);
queue1.put("OneToGo");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
}
//創建一個枚舉類型
enum Helper {
instance;
private static final ExecutorService tPool = Executors.newFixedThreadPool(2);
//數字
public static String[] buildNoArr(int max) {
String[] noArr = new String[max];
for(int i=0;i<max;i++){
noArr[i] = Integer.toString(i+1);
}
return noArr;
}
//字母
public static String[] buildCharArr(int max) {
String[] charArr = new String[max];
int tmp = 65;
for(int i=0;i<max;i++){
charArr[i] = String.valueOf((char)(tmp+i));
}
return charArr;
}
public static void print(String... input){
if(input==null)
return;
for(String each:input){
System.out.print(each);
}
}
public void run(Runnable r){
tPool.submit(r);
}
public void shutdown(){
tPool.shutdown();
}
}
public class BlockingQueueTest {
public static void main(String args[]) throws InterruptedException {
MethodSeven seven = new MethodSeven();
Helper.instance.run(seven.newThreadOne());
Helper.instance.run(seven.newThreadTwo());
Thread.sleep(2000);
System.out.println("");
Helper.instance.run(seven.newThreadThree());
Helper.instance.run(seven.newThreadFour());
Helper.instance.shutdown();
}
}
輸出結果:
12A34B56C78D910E1112F1314G1516H1718I1920J2122K2324L2526M2728N2930O3132P3334Q3536R3738S3940T4142U4344V4546W4748X4950Y5152Z
12A34B56C78D910E1112F1314G1516H1718I1920J2122K2324L2526M2728N2930O3132P3334Q3536R3738S3940T4142U4344V4546W4748X4950Y5152Z
參考資料: