當前位置: 妍妍網 > 碼農

三種方式模擬兩個執行緒搶票

2024-04-02碼農

(給 哪咤編程 加星標,提高Java技能)

大家好,我是哪咤。

假期來臨,搶票出行又將迎來一波新的高潮,今天就來說說在Java中如何高效搶票~

在多執行緒編程中,資源競爭是一個常見的問題。資源競爭發生在多個執行緒試圖同時存取或修改共享資源時,可能導致數據不一致或其他並行問題。在模擬兩個執行緒搶票的場景中,我們需要考慮如何公平地分配票,並確保每個執行緒都有機會成功獲取票。

本篇文章將透過三種方式來模擬兩個執行緒搶票的過程,以展示不同的並行控制策略。

這三種方式包括:

  • 使用 Synchronized 來確保一次只有一個執行緒可以存取票資源。

  • 使用 ReentrantLock 來實作執行緒間的協調。

  • 使用 Semaphore 來限制同時存取票的執行緒數量。

  • 透過比較這三種方式,我們可以深入了解並行控制的不同實作方式及其優缺點。在實際套用中,需要根據具體場景和需求選擇合適的並行控制策略。

    此外,為了更直觀地展示搶票過程,我們將使用程式碼來描述每種方式的實作邏輯。

    一、Synchronized

    含義:Synchronized 是 Java 中的一個關鍵字,用於實作執行緒同步。當一個方法或程式碼塊被 Synchronized 修飾時,同一時間只能有一個執行緒可以執行這個方法或程式碼塊。

    程式碼如下:

    static classTicketSystemBySynchronized {privateint tickets = 100;publicvoidsellTicket() {while (tickets > 0) { //還有票時進行迴圈 synchronized (this) {try {if (tickets > 0) System.out.println(Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --tickets); Thread.sleep(200); //模擬售票 } catch (InterruptedException e) { e.printStackTrace(); } } } }}

    這個類中有一個私有的整型變量 tickets,表示票的總數,初始值為 100。

    類中有一個公共方法 sellTicket(),這個方法模擬售票過程。當還有票(tickets > 0)時,會進入一個 while 迴圈。在迴圈中,首先透過 synchronized (this) 對當前物件進行同步,保證同一時間只有一個執行緒可以執行以下程式碼塊。

    在同步程式碼塊中,首先檢查票的數量是否大於0。如果是,則輸出當前執行緒的名稱以及售出的票數和剩余票數。然後,透過 --tickets 操作將票的數量減1。

    接下來,執行緒休眠 200 毫秒(模擬售票過程)。休眠結束後,迴圈繼續執行,直到票的數量為 0。

    二、ReentrantLock

    含義:ReentrantLock,也稱為可重入鎖,是一種遞迴無阻塞的同步機制。它可以等同於 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更強大、靈活的鎖機制,可以減少死結發生的機率。

    程式碼如下:

    static classTicketSystemByReentrantLock {privateint tickets = 100;private final ReentrantLock lock = new ReentrantLock(); //定義鎖publicvoidsellTicket() {while (tickets > 0) {lock.lock(); //上鎖try { Thread.sleep(200); //模擬售票if (tickets > 0) System.out.println(Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --tickets); } catch (InterruptedException e) { e.printStackTrace(); } finally {lock.unlock(); //解鎖 } } }}

    這個類中有一個私有的整型變量 tickets,表示票的總數,初始值為 100。 另外定義了一個私有的 final 型別的 ReentrantLock 物件 lock,這個物件用於控制對共享資源的存取。

    類中有一個公共方法 sellTicket(),這個方法模擬售票過程。當還有票(tickets > 0)時,會進入一個 while 迴圈。在迴圈中,首先透過 lock.lock() 獲取鎖,保證同一時間只有一個執行緒可以執行以下程式碼塊。

    在鎖保護的程式碼塊中,首先執行緒休眠 200 毫秒(模擬售票過程)。然後檢查票的數量是否大於 0。如果是,則輸出當前執行緒的名稱以及售出的票數和剩余票數。然後,透過 --tickets 操作將票的數量減 1。

    最後,都會透過 lock.unlock() 釋放鎖。防止死結!

    三、Semaphore

    含義:Semaphore 是一種計數號誌,用於管理一組資源。它是一種在多執行緒環境下使用的設施,該設施負責協調各個執行緒,以保證它們能夠正確、合理地使用公共資源。Semaphore 內部基於 AQS(Abstract Queued Synchronizer)的共享模式,相當於給執行緒規定一個量從而控制允許活動的執行緒數。

    程式碼如下:

    static classTicketSystemBySemaphore {private final Semaphore semaphore;publicTicketSystemBySemaphore() {this.semaphore = new Semaphore(100); //總共100張票 }publicvoidsellTicket() {int i = semaphore.availablePermits(); //返回此號誌中當前可用的授權證數while (i > 0) {try { Thread.sleep(200); semaphore.acquire(); // 獲取號誌,如果號誌為0,執行緒將阻塞等待 System.out.println( Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --i); } catch (InterruptedException e) {thrownew RuntimeException(e); } finally { semaphore.release(); // 釋放號誌,允許其他執行緒獲取號誌 } } }}

    Semaphore 是一個計數號誌,用於控制資源的並行存取。在建構函式中,初始化了這個 Semaphore,設定總的可用票數為 100。

    sellTicket() 方法模擬售票過程。首先獲取當前可用的票數,然後進入一個 while 迴圈,只要還有可用的票,就會嘗試獲取一個票。如果當前沒有可用的票,執行緒將會阻塞等待。一旦獲取了票,就輸出售出的資訊。最後釋放號誌。

    四、抽象工廠模式最佳化

    含義:抽象工廠模式是一種建立型設計模式,它為建立一系列相關或互相依賴的物件提供了一種最佳解決方案。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。


    因為要對三種實作型別的程式碼進行測試,不想多寫 if...else... 的程式碼,不想每次指定建立的物件,也為了防止以後有更多實作方法的不方便。提高程式碼的可維護性和可延伸性。

    所以這裏采用抽象工廠模式來進行最佳化。

    程式碼如下:

    首先實作一個介面類:

    publicinterfaceTicketSystem {voidsellTicket();}

    因為三個模擬實作中都定義了 sellTicket 這個方法,所以在介面類裏面定義一個方法,然後由實作類去重寫該方法。

    接下來實作靜態工廠類:

    static class CodeSandboxFactory {static TicketSystem newInstance(Stringtype) {switch (type) {case"Synchronized":returnnew TicketSystemBySynchronized();case"ReentrantLock":returnnew TicketSystemByReentrantLock();case"Semaphore":default:returnnew TicketSystemBySemaphore(); } }}

    這個 CodeSandboxFactory 類是一個靜態工廠類,用於建立TicketSystem物件的不同例項。它接受一個字串參數 type,根據該參數的值決定建立哪種型別的TicketSystem 物件。

  • 如果type參數的值為"Synchronized",則返回一個新的 TicketSystemBySynchronized物件;

  • 如果type參數的值為"ReentrantLock",則返回一個新的 TicketSystemByReentrantLock 物件;

  • 如果type參數的值為"Semaphore",則返回一個新的 TicketSystemBySemaphore物件;

  • 如果type參數的值不是以上三種之一,則預設返回一個新的TicketSystemBySemaphore 物件。

  • 這種設計使得客戶端程式碼可以方便地透過傳遞不同的型別字串來獲取不同型別的 TicketSystem 物件,而不需要關心這些物件的實際建立過程。

    這有助於降低客戶端程式碼與具體實作之間的耦合度,提高程式碼的可維護性和可延伸性。

    五、整體程式碼

    程式碼如下:

    public classThreadsGrabTickets{publicstaticvoidmain(String[] args){ TicketSystem system = CodeSandboxFactory.newInstance("Synchronized");// TicketSystem system =// CodeSandboxFactory.newInstance("ReentrantLock"); TicketSystem// system = CodeSandboxFactory.newInstance("Semaphore");new Thread(system::sellTicket, "執行緒1").start();new Thread(system::sellTicket, "執行緒2").start(); }static classCodeSandboxFactory{static TicketSystem newInstance(String type){switch (type) {case"Synchronized":returnnew TicketSystemBySynchronized();case"ReentrantLock":returnnew TicketSystemByReentrantLock();case"Semaphore":default:returnnew TicketSystemBySemaphore(); } } }static classTicketSystemBySynchronizedimplementsTicketSystem{privateint tickets = 100;@OverridepublicvoidsellTicket(){while (tickets > 0) {synchronized (this) {try {if (tickets > 0) System.out.println(Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --tickets); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } }static classTicketSystemByReentrantLockimplementsTicketSystem{privateint tickets = 100;privatefinal ReentrantLock lock = new ReentrantLock(); //定義鎖@OverridepublicvoidsellTicket(){while (tickets > 0) { lock.lock(); //上鎖try { Thread.sleep(200); //模擬售票if (tickets > 0) System.out.println(Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --tickets); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //解鎖 } } } }static classTicketSystemBySemaphoreimplementsTicketSystem{privatefinal Semaphore semaphore;publicTicketSystemBySemaphore(){this.semaphore = new Semaphore(100); //總共100張票 }@OverridepublicvoidsellTicket(){int i = semaphore.availablePermits(); //返回此號誌中當前可用的授權證數while (i > 0) {try { Thread.sleep(200); semaphore.acquire(); // 獲取號誌,如果號誌為0,執行緒將阻塞等待 System.out.println(Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --i); } catch (InterruptedException e) {thrownew RuntimeException(e); } finally { semaphore.release(); // 釋放號誌,允許其他執行緒獲取號誌 } } } }}

    六、總結

    本文透過模擬兩個執行緒搶票的場景,展示了三種不同的並行控制策略: 使用 Synchronized、ReentrantLock 和 Semaphore。

    透過比較這三種方式,我們可以深入了解並行控制的不同實作方式。

    在實際套用中,需要根據具體場景和需求選擇合適的並行控制策略。

    來源:blog.csdn.net/kologin/article/details/135953580

    (版權歸原作者所有,侵刪)

    - EOF -

    推薦閱讀 點選標題可跳轉

    ·················END·················

    看完本文有收獲?請轉發分享給更多人

    關註「哪咤編程」,提升Java技能

    點贊和在看就是最大的支持 ❤️