阻塞佇列
在阻塞佇列的幫助下,許多同步問題都可以被公式化。阻塞佇列是佇列,當執行緒試圖對空佇列進行出列操作,或試圖向滿的佇列中插入條目時,佇列就會阻塞。直到其他執行緒向佇列中放入資料時才可以移除,同樣,直到其他執行緒從佇列中移除條目之後才可以加入。通過使用 輪詢或等待-通知機制可以實現阻塞佇列。就輪詢機制來說,讀執行緒週期性的呼叫佇列的get方法,直到佇列的訊息變為可用。至於等待-通知機制,讀執行緒僅僅是等待佇列物件,佇列物件會在有條目時通知執行緒。
阻塞佇列的特徵
阻塞佇列的典型特徵可以概括如下:
- 阻塞佇列提供了方法來向其中新增條目。這些方法的呼叫都是阻塞呼叫的,其中條目的插入必須等待,直到佇列的空間變為可用。
- 佇列提供了方法來從中刪除條目,對這些方法的呼叫同樣是阻塞呼叫的。呼叫者會等待條目被放入空佇列
- add和remove方法可以選擇性地為它們的等待操作提供超時並可能被中斷
- put和take操作在單獨的執行緒中實現,從而在兩種型別的操作之間提供了良好的絕緣性
- 不能像阻塞佇列中插入null元素
- 阻塞佇列可能受容量的限制
- 阻塞佇列的實現是執行緒安全的,然而批量操作,比如addAll,沒有必要一定原子地執行,
- 阻塞佇列在本質上不支援“關閉”或“停止”操作,這表示沒有更多的條目可新增
LinkedBlockingQueue
LinkedBlockingQueue
是通過將阻塞佇列的最大容量變為可變,進而擴充套件了資料阻塞佇列的概念。你仍然可以在指定容量已禁止過度擴容。如果不指定容量,預設值是最大的整數值。沒有容量限制是由好處的,因為如果先飛著晚於預訂時間選取條目,生產者無需等待。與基於陣列的佇列相同,過載的建構函式可以接受集合指定的初始值。這種佇列比基於陣列阻塞佇列具有更高的吞吐量。但同時也有較少的可預見性。除了移除操作線上性時間執行之外,佇列的大多數操作都是在常數時間內執行的。
PriorityBlockingQueue
PriorityBlockingQueue
是無界佇列,可以決定元素的優先順序,優先順序可以由元素的自然順序或你提供的比較器來確定。依照優先順序佇列的順序,檢視插入不可比較的物件會導致ClassCastException
異常。如果系統資源耗盡,雖然是無界佇列,新增操作也會失敗,
股票交易系統
package com.guo.chap18;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Created by guo on 17/2/2018.
* 基於阻塞佇列的股票交易伺服器
* 需求:
* 1、允許交易者往佇列中新增出售訂單,也可以獲取待辦的訂單
* 2、在任何給定的時間,如果佇列已滿,交易者就不得不等待某個位置變為空
* 3、購買者必須等待,直到佇列中有出售訂單可用。
* 4、為了簡化情形,假設買方總是必須購買全額數量的可供出售的股票,不可以部分購買。
*/
public class StockExchange {
public static void main(String[] args) {
System.out.printf("Hit Enter to terminate %n%n");
//1、建立LinkedBlockingQueue例項,因為是無限容量,所以交易者可以把任何數量的訂單放入佇列中,
// 如果使用ArrayBlockingQueue,那麼將會限制每隻股票擁有有限次數的交易。
BlockingQueue<Integer> orderQueue = new LinkedBlockingQueue<>();
//2、建立Seller賣家例項,Seller是Runnable的實現類。
Seller seller = new Seller(orderQueue);
//3、建立100個交易者例項,將自己出售的訂單放入佇列中,每個出售訂單都將會有隨機的交易量。
Thread[] sellerThread = new Thread[100];
for (int i = 0; i < 100; i++) {
sellerThread[i] = new Thread(seller);
sellerThread[i].start();
}
//4、建立100個買家例項,選取待售的訂單
Buyer buyer = new Buyer(orderQueue);
Thread[] buyserThread = new Thread[100];
for (int i = 0; i < 100; i++) {
buyserThread[i] = new Thread(buyer);
buyserThread[i].start();
}
try {
//5、一旦建立生產者和消費者執行緒,他們會永遠保持執行,將訂單放入佇列以及從佇列中獲取訂單
// 根據給定時間的負載情況,定期自我阻塞,終止應用程式的方法是使用者在鍵盤上按下Enter鍵。
while (System.in.read() != '\n');
} catch (IOException e) {
e.printStackTrace();
}
//6、main函式會中斷所有正在執行的生產者和消費者執行緒,要求它們中指並退出
System.out.println("Terminating");
for (Thread t : sellerThread) {
t.interrupt();
}
for (Thread t : buyserThread) {
t.interrupt();
}
}
}
複製程式碼
賣家和買家
/**
* 賣家
* Seller類實現了Runnable介面並提供了以OrderQueue作為引數的建構函式
*/
class Seller implements Runnable {
private BlockingQueue orderQueue;
private boolean shutdownRequest = false;
private static int id;
public Seller(BlockingQueue orderQueue) {
this.orderQueue = orderQueue;
}
@Override
public void run() {
while (shutdownRequest == false) {
//1、在每一次迭代中,為每一次的交易量生產一個隨機數
Integer quantity = (int) (Math.random() * 100);
try {
//2、呼叫put方法,將訂單放入佇列中,這是阻塞呼叫,只有在佇列容量有限的情況下,
// 執行緒才需要等待佇列中有出現空的位置
orderQueue.put(quantity);
//3、為了方便使用者,在控制檯列印銷售訂單的詳細資訊,以及用於放置銷售訂單的執行緒詳細資訊
System.out.println("Sell order by" + Thread.currentThread().getName() + ": " + quantity);
} catch (InterruptedException e) {
//4、run方法將無限期的執行,定期的向佇列中提交訂單,通過呼叫interrupt方法,這個執行緒可以被另外一個執行緒中斷。
// interrupt方法產生的InterruptException異常簡單的將shutdownRequest標誌設定為true,將導致run方法無限迴圈終止
shutdownRequest = true;
}
}
}
}
/**
* 買家
* Buyer類實現了Runnable介面並提供了以OrderQueue作為引數的建構函式
*/
class Buyer implements Runnable{
private BlockingQueue orderQueue;
private boolean shutdownRequest = false;
public Buyer(BlockingQueue orderQueue) {
this.orderQueue = orderQueue;
}
@Override
public void run() {
while (shutdownRequest == false) {
try {
//1、run方法通過呼叫take方法,從佇列的頭部取出待辦的交易,
// 如果佇列中沒有可用的訂單,take方法將阻塞,
Integer quantity = ((Integer) orderQueue.take());
//2、為了方便,列印訂單和執行緒的詳細資訊
System.out.println("Buy order by " + Thread.currentThread().getName() + ": " + quantity);
} catch (InterruptedException e) {
shutdownRequest = true;
}
}
}
}
複製程式碼
輸出
...
Buy order by Thread-134: 48
Buy order by Thread-134: 83
Buy order by Thread-134: 2
Buy order by Thread-134: 52
Sell order byThread-86: 90
Sell order byThread-86: 19
Sell order byThread-86: 64
Sell order byThread-86: 83
Sell order byThread-86: 27
Buy order by Thread-163: 94
Buy order by Thread-163: 74
...
複製程式碼
當在鍵盤上按下Enter鍵使,程式終止
在這個程式中,如果沒有使用阻塞佇列,訪問非同步佇列中放置的交易時會發生競爭,每個人都會嘗試搶得低於當前市場價格出售的股票,多個交易者會選取統一訂單,交易之間會很混亂,由於阻塞佇列 確保了同步地訪問佇列,因此交易的完整性絕不會 受到損害。
在這個例子中使用了LinkedBlockingQueue
,氣死也可以基於優先順序的佇列,這樣會自動按照交易的買價和賣價對交易進行排列,具有最好買價和最低賣價的訂單總是排在佇列的頭部。要使用基於優先順序的佇列,需要提供適當的比較器。
說明
1、GitHub程式碼歡迎star。你們輕輕的一點,對我鼓勵特大。
2、個人認為學習語言最好的方式就是模仿、思考別人為什麼這麼寫。結合栗子效果更好,也能記住知識點。
3、只因為自己知識欠缺,語言組織能力不行,所以只能以這樣方式記錄。感覺效果很好。
4、4、本文基於《Java 7 程式設計高階進階》所寫,寫的非常不錯,建議大家去看看