基於LinkedBlockingQueue實現股票交易系統

Guo_1_9發表於2018-02-17

阻塞佇列

在阻塞佇列的幫助下,許多同步問題都可以被公式化。阻塞佇列是佇列,當執行緒試圖對空佇列進行出列操作,或試圖向滿的佇列中插入條目時,佇列就會阻塞。直到其他執行緒向佇列中放入資料時才可以移除,同樣,直到其他執行緒從佇列中移除條目之後才可以加入。通過使用 輪詢或等待-通知機制可以實現阻塞佇列。就輪詢機制來說,讀執行緒週期性的呼叫佇列的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 程式設計高階進階》所寫,寫的非常不錯,建議大家去看看

相關文章