三種方法模擬雙線程搶票
前言
在多線程編程中,資源競爭是一個常見的問題。資源競爭發生在多個線程試圖同時訪問或修改共享資源時,可能導致數據不一致或其他并發問題。在模擬兩個線程搶票的場景中,我們需要考慮如何公平地分配票,并確保每個線程都有機會成功獲取票。
本篇文章將通過三種方式來模擬兩個線程搶票的過程,以展示不同的并發控制策略。
這三種方式包括:
- 使用 Synchronized 來確保一次只有一個線程可以訪問票資源。
- 使用 ReentrantLock 來實現線程間的協調。
- 使用 Semaphore 來限制同時訪問票的線程數量。
通過比較這三種方式,我們可以深入了解并發控制的不同實現方式及其優缺點。在實際應用中,需要根據具體場景和需求選擇合適的并發控制策略。
此外,為了更直觀地展示搶票過程,我們將使用代碼來描述每種方式的實現邏輯。
一、Synchronized
含義:Synchronized 是 Java 中的一個關鍵字,用于實現線程同步。當一個方法或代碼塊被 Synchronized 修飾時,同一時間只能有一個線程可以執行這個方法或代碼塊。
圖片
代碼如下:
static class TicketSystemBySynchronized {
private int tickets = 100;
public void sellTicket() {
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 class TicketSystemByReentrantLock {
private int tickets = 100;
private final ReentrantLock lock = new ReentrantLock(); //定義鎖
public void sellTicket() {
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 class TicketSystemBySemaphore {
private final Semaphore semaphore;
public TicketSystemBySemaphore() {
this.semaphore = new Semaphore(100); //總共100張票
}
public void sellTicket() {
int i = semaphore.availablePermits(); //返回此信號量中當前可用的許可證數
while (i > 0) {
try {
Thread.sleep(200);
semaphore.acquire(); // 獲取信號量,如果信號量為0,線程將阻塞等待
System.out.println(
Thread.currentThread().getName() + "賣出一張票,剩余票數:" + --i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(); // 釋放信號量,允許其他線程獲取信號量
}
}
}
}
Semaphore 是一個計數信號量,用于控制資源的并發訪問。在構造函數中,初始化了這個 Semaphore,設置總的可用票數為 100。
sellTicket() 方法模擬售票過程。首先獲取當前可用的票數,然后進入一個 while 循環,只要還有可用的票,就會嘗試獲取一個票。如果當前沒有可用的票,線程將會阻塞等待。一旦獲取了票,就輸出售出的信息。最后釋放信號量。
四、抽象工廠模式優化
含義:抽象工廠模式是一種創建型設計模式,它為創建一系列相關或互相依賴的對象提供了一種最佳解決方案。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
圖片
因為要對三種實現類型的代碼進行測試,不想多寫 if...else... 的代碼,不想每次指定創建的對象,也為了防止以后有更多實現方法的不方便。提高代碼的可維護性和可擴展性。
所以這里采用抽象工廠模式來進行優化。
代碼如下:
首先實現一個接口類:
public interface TicketSystem {
void sellTicket();
}
因為三個模擬實現中都定義了 sellTicket 這個方法,所以在接口類里面定義一個方法,然后由實現類去重寫該方法。
接下來實現靜態工廠類:
static class CodeSandboxFactory {
static TicketSystem newInstance(String type) {
switch (type) {
case "Synchronized":
return new TicketSystemBySynchronized();
case "ReentrantLock":
return new TicketSystemByReentrantLock();
case "Semaphore":
default:
return new TicketSystemBySemaphore();
}
}
}
這個 CodeSandboxFactory 類是一個靜態工廠類,用于創建TicketSystem對象的不同實例。它接受一個字符串參數 type,根據該參數的值決定創建哪種類型的TicketSystem 對象。
- 如果type參數的值為"Synchronized",則返回一個新的 TicketSystemBySynchronized對象;
- 如果type參數的值為"ReentrantLock",則返回一個新的 TicketSystemByReentrantLock 對象;
- 如果type參數的值為"Semaphore",則返回一個新的 TicketSystemBySemaphore對象;
- 如果type參數的值不是以上三種之一,則默認返回一個新的TicketSystemBySemaphore 對象。
這種設計使得客戶端代碼可以方便地通過傳遞不同的類型字符串來獲取不同類型的 TicketSystem 對象,而不需要關心這些對象的實際創建過程。
這有助于降低客戶端代碼與具體實現之間的耦合度,提高代碼的可維護性和可擴展性。
五、整體代碼
代碼如下:
public class ThreadsGrabTickets {
public static void main(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 class CodeSandboxFactory {
static TicketSystem newInstance(String type) {
switch (type) {
case "Synchronized":
return new TicketSystemBySynchronized();
case "ReentrantLock":
return new TicketSystemByReentrantLock();
case "Semaphore":
default:
return new TicketSystemBySemaphore();
}
}
}
static class TicketSystemBySynchronized implements TicketSystem {
private int tickets = 100;
@Override
public void sellTicket() {
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 class TicketSystemByReentrantLock implements TicketSystem {
private int tickets = 100;
private final ReentrantLock lock = new ReentrantLock(); //定義鎖
@Override
public void sellTicket() {
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 class TicketSystemBySemaphore implements TicketSystem {
private final Semaphore semaphore;
public TicketSystemBySemaphore() {
this.semaphore = new Semaphore(100); //總共100張票
}
@Override
public void sellTicket() {
int i = semaphore.availablePermits(); //返回此信號量中當前可用的許可證數
while (i > 0) {
try {
Thread.sleep(200);
semaphore.acquire(); // 獲取信號量,如果信號量為0,線程將阻塞等待
System.out.println(Thread.currentThread().getName()
+ "賣出一張票,剩余票數:" + --i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(); // 釋放信號量,允許其他線程獲取信號量
}
}
}
}
}
六、總結
本文通過模擬兩個線程搶票的場景,展示了三種不同的并發控制策略:使用 Synchronized、ReentrantLock 和 Semaphore。
通過比較這三種方式,我們可以深入了解并發控制的不同實現方式。
在實際應用中,需要根據具體場景和需求選擇合適的并發控制策略。