三種方法模擬雙線(xiàn)程搶票
前言
在多線(xiàn)程編程中,資源競(jìng)爭(zhēng)是一個(gè)常見(jiàn)的問(wèn)題。資源競(jìng)爭(zhēng)發(fā)生在多個(gè)線(xiàn)程試圖同時(shí)訪問(wèn)或修改共享資源時(shí),可能導(dǎo)致數(shù)據(jù)不一致或其他并發(fā)問(wèn)題。在模擬兩個(gè)線(xiàn)程搶票的場(chǎng)景中,我們需要考慮如何公平地分配票,并確保每個(gè)線(xiàn)程都有機(jī)會(huì)成功獲取票。
本篇文章將通過(guò)三種方式來(lái)模擬兩個(gè)線(xiàn)程搶票的過(guò)程,以展示不同的并發(fā)控制策略。
這三種方式包括:
- 使用 Synchronized 來(lái)確保一次只有一個(gè)線(xiàn)程可以訪問(wèn)票資源。
- 使用 ReentrantLock 來(lái)實(shí)現(xiàn)線(xiàn)程間的協(xié)調(diào)。
- 使用 Semaphore 來(lái)限制同時(shí)訪問(wèn)票的線(xiàn)程數(shù)量。
通過(guò)比較這三種方式,我們可以深入了解并發(fā)控制的不同實(shí)現(xiàn)方式及其優(yōu)缺點(diǎn)。在實(shí)際應(yīng)用中,需要根據(jù)具體場(chǎng)景和需求選擇合適的并發(fā)控制策略。
此外,為了更直觀地展示搶票過(guò)程,我們將使用代碼來(lái)描述每種方式的實(shí)現(xiàn)邏輯。
一、Synchronized
含義:Synchronized 是 Java 中的一個(gè)關(guān)鍵字,用于實(shí)現(xiàn)線(xiàn)程同步。當(dāng)一個(gè)方法或代碼塊被 Synchronized 修飾時(shí),同一時(shí)間只能有一個(gè)線(xiàn)程可以執(zhí)行這個(gè)方法或代碼塊。
圖片
代碼如下:
static class TicketSystemBySynchronized {
private int tickets = 100;
public void sellTicket() {
while (tickets > 0) { //還有票時(shí)進(jìn)行循環(huán)
synchronized (this) {
try {
if (tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "賣(mài)出一張票,剩余票數(shù):" + --tickets);
Thread.sleep(200); //模擬售票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
這個(gè)類(lèi)中有一個(gè)私有的整型變量 tickets,表示票的總數(shù),初始值為 100。
類(lèi)中有一個(gè)公共方法 sellTicket(),這個(gè)方法模擬售票過(guò)程。當(dāng)還有票(tickets > 0)時(shí),會(huì)進(jìn)入一個(gè) while 循環(huán)。在循環(huán)中,首先通過(guò) synchronized (this) 對(duì)當(dāng)前對(duì)象進(jìn)行同步,保證同一時(shí)間只有一個(gè)線(xiàn)程可以執(zhí)行以下代碼塊。
在同步代碼塊中,首先檢查票的數(shù)量是否大于0。如果是,則輸出當(dāng)前線(xiàn)程的名稱(chēng)以及售出的票數(shù)和剩余票數(shù)。然后,通過(guò) --tickets 操作將票的數(shù)量減1。
接下來(lái),線(xiàn)程休眠 200 毫秒(模擬售票過(guò)程)。休眠結(jié)束后,循環(huán)繼續(xù)執(zhí)行,直到票的數(shù)量為 0。
二、ReentrantLock
含義:ReentrantLock,也稱(chēng)為可重入鎖,是一種遞歸無(wú)阻塞的同步機(jī)制。它可以等同于 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更強(qiáng)大、靈活的鎖機(jī)制,可以減少死鎖發(fā)生的概率。
圖片
代碼如下:
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()
+ "賣(mài)出一張票,剩余票數(shù):" + --tickets);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //解鎖
}
}
}
}
這個(gè)類(lèi)中有一個(gè)私有的整型變量 tickets,表示票的總數(shù),初始值為 100。另外定義了一個(gè)私有的 final 類(lèi)型的 ReentrantLock 對(duì)象 lock,這個(gè)對(duì)象用于控制對(duì)共享資源的訪問(wèn)。
類(lèi)中有一個(gè)公共方法 sellTicket(),這個(gè)方法模擬售票過(guò)程。當(dāng)還有票(tickets > 0)時(shí),會(huì)進(jìn)入一個(gè) while 循環(huán)。在循環(huán)中,首先通過(guò) lock.lock() 獲取鎖,保證同一時(shí)間只有一個(gè)線(xiàn)程可以執(zhí)行以下代碼塊。
在鎖保護(hù)的代碼塊中,首先線(xiàn)程休眠 200 毫秒(模擬售票過(guò)程)。然后檢查票的數(shù)量是否大于 0。如果是,則輸出當(dāng)前線(xiàn)程的名稱(chēng)以及售出的票數(shù)和剩余票數(shù)。然后,通過(guò) --tickets 操作將票的數(shù)量減 1。
最后,都會(huì)通過(guò) lock.unlock() 釋放鎖。防止死鎖!
三、Semaphore
含義:Semaphore 是一種計(jì)數(shù)信號(hào)量,用于管理一組資源。它是一種在多線(xiàn)程環(huán)境下使用的設(shè)施,該設(shè)施負(fù)責(zé)協(xié)調(diào)各個(gè)線(xiàn)程,以保證它們能夠正確、合理地使用公共資源。Semaphore 內(nèi)部基于 AQS(Abstract Queued Synchronizer)的共享模式,相當(dāng)于給線(xiàn)程規(guī)定一個(gè)量從而控制允許活動(dòng)的線(xiàn)程數(shù)。
圖片
代碼如下:
static class TicketSystemBySemaphore {
private final Semaphore semaphore;
public TicketSystemBySemaphore() {
this.semaphore = new Semaphore(100); //總共100張票
}
public void sellTicket() {
int i = semaphore.availablePermits(); //返回此信號(hào)量中當(dāng)前可用的許可證數(shù)
while (i > 0) {
try {
Thread.sleep(200);
semaphore.acquire(); // 獲取信號(hào)量,如果信號(hào)量為0,線(xiàn)程將阻塞等待
System.out.println(
Thread.currentThread().getName() + "賣(mài)出一張票,剩余票數(shù):" + --i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(); // 釋放信號(hào)量,允許其他線(xiàn)程獲取信號(hào)量
}
}
}
}
Semaphore 是一個(gè)計(jì)數(shù)信號(hào)量,用于控制資源的并發(fā)訪問(wèn)。在構(gòu)造函數(shù)中,初始化了這個(gè) Semaphore,設(shè)置總的可用票數(shù)為 100。
sellTicket() 方法模擬售票過(guò)程。首先獲取當(dāng)前可用的票數(shù),然后進(jìn)入一個(gè) while 循環(huán),只要還有可用的票,就會(huì)嘗試獲取一個(gè)票。如果當(dāng)前沒(méi)有可用的票,線(xiàn)程將會(huì)阻塞等待。一旦獲取了票,就輸出售出的信息。最后釋放信號(hào)量。
四、抽象工廠模式優(yōu)化
含義:抽象工廠模式是一種創(chuàng)建型設(shè)計(jì)模式,它為創(chuàng)建一系列相關(guān)或互相依賴(lài)的對(duì)象提供了一種最佳解決方案。這種類(lèi)型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。
圖片
因?yàn)橐獙?duì)三種實(shí)現(xiàn)類(lèi)型的代碼進(jìn)行測(cè)試,不想多寫(xiě) if...else... 的代碼,不想每次指定創(chuàng)建的對(duì)象,也為了防止以后有更多實(shí)現(xiàn)方法的不方便。提高代碼的可維護(hù)性和可擴(kuò)展性。
所以這里采用抽象工廠模式來(lái)進(jìn)行優(yōu)化。
代碼如下:
首先實(shí)現(xiàn)一個(gè)接口類(lèi):
public interface TicketSystem {
void sellTicket();
}
因?yàn)槿齻€(gè)模擬實(shí)現(xiàn)中都定義了 sellTicket 這個(gè)方法,所以在接口類(lèi)里面定義一個(gè)方法,然后由實(shí)現(xiàn)類(lèi)去重寫(xiě)該方法。
接下來(lái)實(shí)現(xiàn)靜態(tài)工廠類(lèi):
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();
}
}
}
這個(gè) CodeSandboxFactory 類(lèi)是一個(gè)靜態(tài)工廠類(lèi),用于創(chuàng)建TicketSystem對(duì)象的不同實(shí)例。它接受一個(gè)字符串參數(shù) type,根據(jù)該參數(shù)的值決定創(chuàng)建哪種類(lèi)型的TicketSystem 對(duì)象。
- 如果type參數(shù)的值為"Synchronized",則返回一個(gè)新的 TicketSystemBySynchronized對(duì)象;
- 如果type參數(shù)的值為"ReentrantLock",則返回一個(gè)新的 TicketSystemByReentrantLock 對(duì)象;
- 如果type參數(shù)的值為"Semaphore",則返回一個(gè)新的 TicketSystemBySemaphore對(duì)象;
- 如果type參數(shù)的值不是以上三種之一,則默認(rèn)返回一個(gè)新的TicketSystemBySemaphore 對(duì)象。
這種設(shè)計(jì)使得客戶(hù)端代碼可以方便地通過(guò)傳遞不同的類(lèi)型字符串來(lái)獲取不同類(lèi)型的 TicketSystem 對(duì)象,而不需要關(guān)心這些對(duì)象的實(shí)際創(chuàng)建過(guò)程。
這有助于降低客戶(hù)端代碼與具體實(shí)現(xiàn)之間的耦合度,提高代碼的可維護(hù)性和可擴(kuò)展性。
五、整體代碼
代碼如下:
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, "線(xiàn)程1").start();
new Thread(system::sellTicket, "線(xiàn)程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()
+ "賣(mài)出一張票,剩余票數(shù):" + --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()
+ "賣(mài)出一張票,剩余票數(shù):" + --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(); //返回此信號(hào)量中當(dāng)前可用的許可證數(shù)
while (i > 0) {
try {
Thread.sleep(200);
semaphore.acquire(); // 獲取信號(hào)量,如果信號(hào)量為0,線(xiàn)程將阻塞等待
System.out.println(Thread.currentThread().getName()
+ "賣(mài)出一張票,剩余票數(shù):" + --i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(); // 釋放信號(hào)量,允許其他線(xiàn)程獲取信號(hào)量
}
}
}
}
}
六、總結(jié)
本文通過(guò)模擬兩個(gè)線(xiàn)程搶票的場(chǎng)景,展示了三種不同的并發(fā)控制策略:使用 Synchronized、ReentrantLock 和 Semaphore。
通過(guò)比較這三種方式,我們可以深入了解并發(fā)控制的不同實(shí)現(xiàn)方式。
在實(shí)際應(yīng)用中,需要根據(jù)具體場(chǎng)景和需求選擇合適的并發(fā)控制策略。