亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

多線程售票案例

1. 前言

本節內容主要是使用 Java 的鎖機制對多線程售票案例進行實現。售票案例多數情況下主要關注多線程如何安全的減少庫存,也就是剩余的票數,當票數為 0 時,停止減少庫存。

本節內容除了關注車票庫存的減少,還會涉及到退票窗口,能夠更加貼切的模擬真實的場景。

本節內容需要學習者關注如下兩個重點:

  • 掌握多線程的售票機制模型,在后續的工作中如果涉及到類似的場景,能夠第一時間了解場景的整體結構;
  • 使用 Condition 和 Lock 實現售票機制,鞏固我們本章節內容所學習的新的鎖機制。

2. 售票機制模型

售票機制模型是源于現實生活中的售票場景,從開始的單窗口售票到多窗口售票,從開始的人工統計票數到后續的系統智能在線售票。多并發編程能夠實現這一售票場景,多窗口售票情況下保證線程的安全性和票數的正確性。

圖片描述

如上圖所示,有兩個售票窗口進行售票,有一個窗口處理退票,這既是現實生活中一個簡單的售票機制。

3. 售票機制實現

場景設計

  • 創建一個工廠類 TicketCenter,該類包含兩個方法,saleRollback 退票方法和 sale 售票方法;
  • 定義一個車票總數等于 10 ,為了方便觀察結果,設置為 10。學習者也可自行選擇數量;
  • 對于 saleRollback 方法,當發生退票時,通知售票窗口繼續售賣車票;
  • 對 saleRollback 進行特別設置,每隔 5000 毫秒退回一張車票;
  • 對于 sale 方法,只要有車票就進行售賣。為了更便于觀察結果,每賣出一張車票,sleep 2000 毫秒;
  • 創建一個測試類,main 函數中創建 2 個售票窗口和 1 個退票窗口,運行程序進行結果觀察。
  • 修改 saleRollback 退票時間,每隔 25 秒退回一張車票;
  • 再次運行程序并觀察結果。

實現要求:本實驗要求使用 ReentrantLock 與 Condition 接口實現同步機制。

實例

public class DemoTest {
        public static void main(String[] args) {
            TicketCenter ticketCenter = new TicketCenter();
            new Thread(new saleRollback(ticketCenter),"退票窗口"). start();
            new Thread(new Consumer(ticketCenter),"1號售票窗口"). start();
            new Thread(new Consumer(ticketCenter),"2號售票窗口"). start();
        }
}

class TicketCenter {
    private int capacity = 10; // 根據需求:定義10漲車票
    private Lock lock = new ReentrantLock(false);
    private Condition saleLock = lock.newCondition();
    // 根據需求:saleRollback 方法創建,為退票使用
    public void saleRollback() {
        try {
            lock.lock();
            capacity++;
            System.out.println("線程("+Thread.currentThread().getName() + ")發生退票。" + "當前剩余票數"+capacity+"個");
            saleLock.signalAll(); //發生退票,通知售票窗口進行售票
        } finally {
            lock.unlock();
        }
    }

    // 根據需求:sale 方法創建
    public void sale() {
        try {
            lock.lock();
            while (capacity==0) { //沒有票的情況下,停止售票
                try {
                    System.out.println("警告:線程("+Thread.currentThread().getName() + ")準備售票,但當前沒有剩余車票");
                    saleLock.await(); //剩余票數為 0 ,無法售賣,進入 wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            capacity-- ; //如果有票,則售賣 -1
            System.out.println("線程("+Thread.currentThread().getName() + ")售出一張票。" + "當前剩余票數"+capacity+"個");
        } finally {
            lock.unlock();
        }
    }
}

class saleRollback implements Runnable {
    private TicketCenter TicketCenter; //關聯工廠類,調用 saleRollback 方法
    public saleRollback(TicketCenter TicketCenter) {
        this.TicketCenter = TicketCenter;
    }
    public void run() {
        while (true) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            TicketCenter.saleRollback(); //根據需求 ,調用 TicketCenter 的 saleRollback 方法

        }
    }
}
class Consumer implements Runnable {
    private TicketCenter TicketCenter;
    public Consumer(TicketCenter TicketCenter) {
        this.TicketCenter = TicketCenter;
    }
    public void run() {
        while (true) {
            TicketCenter.sale(); //調用sale 方法
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

結果驗證

線程(1號售票窗口)售出一張票。當前剩余票數9個
線程(2號售票窗口)售出一張票。當前剩余票數8個
線程(2號售票窗口)售出一張票。當前剩余票數7個
線程(1號售票窗口)售出一張票。當前剩余票數6個
線程(1號售票窗口)售出一張票。當前剩余票數5個
線程(2號售票窗口)售出一張票。當前剩余票數4個
線程(退票窗口)發生退票。當前剩余票數5個
線程(1號售票窗口)售出一張票。當前剩余票數4個
線程(2號售票窗口)售出一張票。當前剩余票數3個
線程(2號售票窗口)售出一張票。當前剩余票數2個
線程(1號售票窗口)售出一張票。當前剩余票數1個
線程(退票窗口)發生退票。當前剩余票數2個
線程(1號售票窗口)售出一張票。當前剩余票數1個
線程(2號售票窗口)售出一張票。當前剩余票數0個
警告:線程(1號售票窗口)準備售票,但當前沒有剩余車票
警告:線程(2號售票窗口)準備售票,但當前沒有剩余車票
線程(退票窗口)發生退票。當前剩余票數1個
線程(1號售票窗口)售出一張票。當前剩余票數0個
警告:線程(2號售票窗口)準備售票,但當前沒有剩余車票
警告:線程(1號售票窗口)準備售票,但當前沒有剩余車票

結果分析:從結果來看,我們正確的完成了售票和退票的機制,并且使用了 ReentrantLock 與 Condition 接口。

代碼片段分析 1:看售票方法代碼。

public void sale() {
        try {
            lock.lock();
            while (capacity==0) { //沒有票的情況下,停止售票
                try {
                    System.out.println("警告:線程("+Thread.currentThread().getName() + ")準備售票,但當前沒有剩余車票");
                    saleLock.await(); //剩余票數為 0 ,無法售賣,進入 wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            capacity-- ; //如果有票,則售賣 -1
            System.out.println("線程("+Thread.currentThread().getName() + ")售出一張票。" + "當前剩余票數"+capacity+"個");
        } finally {
            lock.unlock();
        }
    }

主要來看方法中僅僅使用了 await 方法,因為退票是場景觸發的,售票窗口無需喚醒退票窗口,因為真實的場景下,可能沒有退票的發生,所以無需喚醒。這與生產者與消費者模式存在著比較明顯的區別。

代碼片段分析 2:看退票方法代碼。

public void saleRollback() {
        try {
            lock.lock();
            capacity++;
            System.out.println("線程("+Thread.currentThread().getName() + ")發生退票。" + "當前剩余票數"+capacity+"個");
            saleLock.signalAll(); //發生退票,通知售票窗口進行售票
        } finally {
            lock.unlock();
        }
    }

退票方法只有 signalAll 方法,通知售票窗口進行售票,無需調用 await 方法,因為只要有退票的發生,就能夠繼續售票,沒有庫存上限的定義,這也是與生產者與消費者模式的一個主要區別。

總結:售票機制與生產者 - 消費者模式存在著細微的區別,需要學習者通過代碼的實現慢慢體會。由于售票方法只需要進入 await 狀態,退票方法需要喚醒售票的 await 狀態,因此只需要創建一個售票窗口的 Condition 對象。

4. 小結

本節內容主要對售票機制模型進行了講解,核心內容為售票機制的實現。實現的過程使用 ReentrantLock 與 Condition 接口實現同步機制,也是本節課程的重點知識。

至此,并發編程原理課程就結束了,從基本的多線程的創建,synchronized 關鍵字的使用,再到鎖的應用,貫穿了并發編程原理的全部知識,學習完本套課程,可以進一步學習并發編程包的課程。