詳解Java中信號(hào)量Semaphore的使用
第1章:引言
大家好,我是小黑。今天,咱們一起來(lái)深入探討一下Semaphore。在Java中,正確地管理并發(fā)是一件既挑戰(zhàn)又有趣的事情。當(dāng)談到并發(fā)控制,大家可能首先想到的是synchronized關(guān)鍵字或者是ReentrantLock。但其實(shí),Java還提供了一個(gè)非常強(qiáng)大的工具,就是Semaphore。
Semaphore,直譯過(guò)來(lái)就是“信號(hào)量”。在日常生活中,信號(hào)燈控制著車(chē)輛的通行,防止交通混亂,這其實(shí)和Semaphore在程序中的作用頗為相似。Semaphore主要用于控制同時(shí)訪問(wèn)特定資源的線程數(shù)量,它通過(guò)協(xié)調(diào)各個(gè)線程,保證合理的使用公共資源。比方說(shuō)如果有一家餐館只允許固定數(shù)量的顧客同時(shí)用餐,這就是Semaphore的經(jīng)典應(yīng)用場(chǎng)景。
第2章:Semaphore的基本概念
讓我們先來(lái)了解一下Semaphore的基本概念。在Java中,Semaphore是位于java.util.concurrent
包下的一個(gè)類(lèi)。它的核心就是維護(hù)了一個(gè)許可集。簡(jiǎn)單來(lái)說(shuō),就是有一定數(shù)量的許可,線程需要先獲取到許可,才能執(zhí)行,執(zhí)行完畢后再釋放許可。
那么,這個(gè)許可是什么呢?其實(shí),你可以把它想象成是對(duì)資源的訪問(wèn)權(quán)。比如,有5個(gè)許可,就意味著最多允許5個(gè)線程同時(shí)執(zhí)行。線程可以通過(guò)acquire()
方法來(lái)獲取許可,如果沒(méi)有可用的許可,該線程就會(huì)阻塞,直到有許可可用。
讓我們看個(gè)簡(jiǎn)單的例子。假設(shè)咱們有一個(gè)限制了最多同時(shí)3個(gè)線程執(zhí)行的Semaphore:
import java.util.concurrent.Semaphore; public class SemaphoreExample { // 創(chuàng)建一個(gè)Semaphore實(shí)例,許可數(shù)量為3 private static final Semaphore semaphore = new Semaphore(3); public static void main(String[] args) { // 創(chuàng)建并啟動(dòng)三個(gè)線程 for (int i = 1; i <= 3; i++) { new Thread(new Task(semaphore), "線程" + i).start(); } } static class Task implements Runnable { private final Semaphore semaphore; public Task(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { // 請(qǐng)求許可 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 獲取許可,正在執(zhí)行"); Thread.sleep(1000); // 模擬任務(wù)執(zhí)行 System.out.println(Thread.currentThread().getName() + " 執(zhí)行完畢,釋放許可"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 釋放許可 semaphore.release(); } } } }
在這個(gè)例子中,咱們創(chuàng)建了一個(gè)Semaphore實(shí)例,設(shè)置最大許可數(shù)為3。這意味著,最多只能有3個(gè)線程同時(shí)運(yùn)行Task中的代碼。每個(gè)線程在開(kāi)始執(zhí)行前,都會(huì)嘗試通過(guò)acquire()
方法獲取一個(gè)許可。
第3章:Semaphore的核心原理
現(xiàn)在,咱們深入一下Semaphore的核心原理。理解這個(gè)原理對(duì)于掌握Semaphore的高效使用至關(guān)重要。在Java中,Semaphore不僅僅是個(gè)計(jì)數(shù)器,它背后的原理和實(shí)現(xiàn)邏輯比看起來(lái)要復(fù)雜得多。
Semaphore的核心是基于AQS(AbstractQueuedSynchronizer)這個(gè)框架。AQS是Java并發(fā)包中的一個(gè)非常重要的組件,它用來(lái)構(gòu)建鎖或者其他同步器。簡(jiǎn)單來(lái)說(shuō),AQS提供了一種機(jī)制,可以讓線程在訪問(wèn)某個(gè)資源前進(jìn)入等待狀態(tài),并在資源可用時(shí)被喚醒。這正是Semaphore的基礎(chǔ)。
Semaphore維護(hù)了一個(gè)許可集,這個(gè)集合的大小在初始化時(shí)設(shè)定。每次調(diào)用acquire()
方法,Semaphore會(huì)試圖從這個(gè)集合中取出一個(gè)許可。如果沒(méi)有可用的許可,線程就會(huì)被阻塞,直到有其他線程釋放一個(gè)許可。相反,release()
方法會(huì)增加許可的數(shù)量,并有可能喚醒等待的線程。
讓小黑通過(guò)一段代碼來(lái)更好地說(shuō)明這個(gè)原理:
import java.util.concurrent.Semaphore; public class SemaphoreDeepDive { public static void main(String[] args) { // 初始化一個(gè)只有2個(gè)許可的Semaphore Semaphore semaphore = new Semaphore(2); Runnable task = () -> { try { // 嘗試獲取許可 semaphore.acquire(); System.out.println("線程 " + Thread.currentThread().getName() + " 獲取了許可"); // 模擬任務(wù)執(zhí)行 Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 釋放許可 semaphore.release(); System.out.println("線程 " + Thread.currentThread().getName() + " 釋放了許可"); } }; // 創(chuàng)建并啟動(dòng)3個(gè)線程 for (int i = 0; i < 3; i++) { new Thread(task).start(); } } }
在這個(gè)例子中,Semaphore被初始化為只有兩個(gè)許可。當(dāng)三個(gè)線程嘗試運(yùn)行時(shí),只有兩個(gè)能夠同時(shí)執(zhí)行。第三個(gè)線程必須等待,直到一個(gè)許可被釋放。這就是Semaphore控制并發(fā)的機(jī)制。
第4章:使用Semaphore的場(chǎng)景
咱們來(lái)聊聊Semaphore在實(shí)際編程中的應(yīng)用場(chǎng)景。理解了Semaphore的基礎(chǔ)和原理后,咱們現(xiàn)在可以探索它在實(shí)際場(chǎng)景中的具體使用。Semaphore非常靈活,可以用于多種場(chǎng)合,特別是在控制資源訪問(wèn)的并發(fā)環(huán)境中。
場(chǎng)景一:資源池
想象一下,小黑有一個(gè)數(shù)據(jù)庫(kù)連接池,這個(gè)池子里只有幾個(gè)數(shù)據(jù)庫(kù)連接。如果所有的連接都被占用了,其他需要數(shù)據(jù)庫(kù)連接的線程就得等待。這就是Semaphore的經(jīng)典應(yīng)用場(chǎng)景。通過(guò)限制可用的連接數(shù)量,Semaphore確保了不會(huì)有太多的線程同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)。
場(chǎng)景二:限流
在Web服務(wù)中,咱們可能想要限制某個(gè)服務(wù)的并發(fā)請(qǐng)求數(shù)量,以防止服務(wù)器過(guò)載。Semaphore可以很容易地實(shí)現(xiàn)這個(gè)功能。設(shè)置一個(gè)固定數(shù)量的許可,就可以限制同時(shí)處理的請(qǐng)求數(shù)量。
代碼示例
讓小黑用代碼展示一下這些場(chǎng)景。首先,是一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)連接池的示例:
import java.util.concurrent.Semaphore; public class DatabaseConnectionPool { private final Semaphore semaphore; private final String[] connectionPool; private final boolean[] used; public DatabaseConnectionPool(int poolSize) { semaphore = new Semaphore(poolSize); connectionPool = new String[poolSize]; used = new boolean[poolSize]; for (int i = 0; i < poolSize; i++) { connectionPool[i] = "連接 " + (i + 1); } } public String getConnection() throws InterruptedException { semaphore.acquire(); return getNextAvailableConnection(); } public void releaseConnection(String connection) { if (markAsUnused(connection)) { semaphore.release(); } } private synchronized String getNextAvailableConnection() { for (int i = 0; i < connectionPool.length; i++) { if (!used[i]) { used[i] = true; return connectionPool[i]; } } return null; // 不應(yīng)該發(fā)生,semaphore保證了有可用連接 } private synchronized boolean markAsUnused(String connection) { for (int i = 0; i < connectionPool.length; i++) { if (connection.equals(connectionPool[i])) { used[i] = false; return true; } } return false; } }
這個(gè)代碼演示了如何使用Semaphore來(lái)控制對(duì)有限數(shù)量資源(數(shù)據(jù)庫(kù)連接)的訪問(wèn)。每個(gè)連接在使用前需要獲得一個(gè)許可,使用完后釋放許可。
第5章:Semaphore的高級(jí)特性
公平性與非公平性
Semaphore有兩種模式:公平模式和非公平模式。公平模式下,線程獲得許可的順序與它們請(qǐng)求許可的順序一致,就像排隊(duì)一樣。而非公平模式則沒(méi)有這種保證,線程可以“插隊(duì)”,這可能會(huì)導(dǎo)致某些線程等待時(shí)間過(guò)長(zhǎng)。
在Java中,創(chuàng)建Semaphore時(shí)可以指定是公平模式還是非公平模式。默認(rèn)情況下,Semaphore是非公平的。公平模式通常會(huì)有更高的性能開(kāi)銷(xiāo),因?yàn)樗枰S護(hù)一個(gè)更加復(fù)雜的內(nèi)部結(jié)構(gòu)來(lái)保證順序。
可中斷操作
在Semaphore中,等待許可的操作可以是可中斷的。這意味著如果一個(gè)線程在等待一個(gè)許可時(shí)被中斷,它可以選擇退出等待。這在處理某些需要響應(yīng)中斷的場(chǎng)景時(shí)非常有用。
代碼示例
讓小黑給你演示一下這兩個(gè)特性的代碼實(shí)例:
import java.util.concurrent.Semaphore; public class SemaphoreAdvancedFeatures { public static void main(String[] args) throws InterruptedException { // 創(chuàng)建一個(gè)公平模式的Semaphore Semaphore fairSemaphore = new Semaphore(1, true); // 創(chuàng)建并啟動(dòng)兩個(gè)線程 Thread t1 = new Thread(new Worker(fairSemaphore), "線程1"); Thread t2 = new Thread(new Worker(fairSemaphore), "線程2"); t1.start(); t2.start(); // 演示可中斷操作 Thread interruptibleThread = new Thread(() -> { try { fairSemaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 獲取了許可"); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 被中斷"); } }); interruptibleThread.start(); Thread.sleep(1000); // 等待一會(huì) interruptibleThread.interrupt(); // 中斷線程 } static class Worker implements Runnable { private final Semaphore semaphore; Worker(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 獲取了許可"); Thread.sleep(2000); // 模擬工作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); System.out.println(Thread.currentThread().getName() + " 釋放了許可"); } } } }
在這個(gè)代碼中,小黑創(chuàng)建了一個(gè)公平模式的Semaphore,并演示了兩個(gè)線程按順序獲取許可的情況。同時(shí),還展示了一個(gè)線程在嘗試獲取許可時(shí)如何被中斷。
第6章:Semaphore的問(wèn)題與解決方案
問(wèn)題一:資源耗盡
最常見(jiàn)的問(wèn)題之一是資源耗盡。當(dāng)所有許可都被占用,并且持有許可的線程因某種原因無(wú)法釋放許可時(shí),就會(huì)出現(xiàn)資源耗盡的情況。這可能會(huì)導(dǎo)致其他線程永久等待,從而造成死鎖。
解決方案:確保在使用資源后總是釋放許可。可以使用try-finally
塊來(lái)確保即使在發(fā)生異常時(shí)也能釋放許可。
問(wèn)題二:公平性問(wèn)題
如前所述,Semaphore可以是公平的或非公平的。在非公平模式下,有可能導(dǎo)致某些線程饑餓,即永遠(yuǎn)得不到執(zhí)行的機(jī)會(huì)。
解決方案:如果需要保證每個(gè)線程都有機(jī)會(huì)執(zhí)行,可以考慮使用公平模式的Semaphore。
問(wèn)題三:性能問(wèn)題
在高并發(fā)場(chǎng)景中,Semaphore可能成為性能瓶頸。由于線程頻繁地獲取和釋放許可,可能會(huì)導(dǎo)致過(guò)多的上下文切換和競(jìng)爭(zhēng)。
解決方案:適當(dāng)調(diào)整許可的數(shù)量,或者尋找其他更適合高并發(fā)場(chǎng)景的并發(fā)工具。
代碼示例
讓小黑通過(guò)代碼來(lái)展示如何妥善處理這些問(wèn)題:
import java.util.concurrent.Semaphore; public class SemaphoreProblemSolving { private static final Semaphore semaphore = new Semaphore(1); public static void main(String[] args) { Thread thread1 = new Thread(SemaphoreProblemSolving::safeMethod, "線程1"); Thread thread2 = new Thread(SemaphoreProblemSolving::safeMethod, "線程2"); thread1.start(); thread2.start(); } private static void safeMethod() { try { semaphore.acquire(); try { // 執(zhí)行關(guān)鍵區(qū)域代碼 System.out.println(Thread.currentThread().getName() + " 在執(zhí)行"); Thread.sleep(1000); } finally { semaphore.release(); // 確??偸轻尫旁S可 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
在這段代碼中,小黑展示了如何使用try-finally
塊來(lái)確保無(wú)論如何都會(huì)釋放Semaphore的許可。這種方式可以減少由于異常導(dǎo)致的資源耗盡問(wèn)題。
第7章:與其他并發(fā)工具的結(jié)合使用
結(jié)合CountDownLatch
CountDownLatch
是一種同步幫助,它允許一個(gè)或多個(gè)線程等待其他線程完成一系列操作。在某些場(chǎng)景中,咱們可能需要先用Semaphore控制資源訪問(wèn),然后使用CountDownLatch
來(lái)同步多個(gè)線程的進(jìn)度。
結(jié)合CyclicBarrier
CyclicBarrier
與CountDownLatch
類(lèi)似,但它允許一組線程相互等待,達(dá)到一個(gè)共同的障礙點(diǎn)再繼續(xù)執(zhí)行。這在需要多個(gè)線程在某個(gè)點(diǎn)同步執(zhí)行的場(chǎng)景中非常有用。結(jié)合Semaphore,可以在達(dá)到共同點(diǎn)之前控制線程對(duì)資源的訪問(wèn)。
代碼示例
讓小黑給咱們展示一個(gè)結(jié)合使用Semaphore和CountDownLatch的例子:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; public class CombinedSemaphoreCountDownLatch { private static final int THREAD_COUNT = 5; private static final Semaphore semaphore = new Semaphore(2); private static final CountDownLatch latch = new CountDownLatch(THREAD_COUNT); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < THREAD_COUNT; i++) { new Thread(new Worker(i, semaphore, latch)).start(); } latch.await(); // 等待所有線程完成 System.out.println("所有線程執(zhí)行完畢"); } static class Worker implements Runnable { private final int workerNumber; private final Semaphore semaphore; private final CountDownLatch latch; Worker(int workerNumber, Semaphore semaphore, CountDownLatch latch) { this.workerNumber = workerNumber; this.semaphore = semaphore; this.latch = latch; } @Override public void run() { try { semaphore.acquire(); System.out.println("工人 " + workerNumber + " 正在工作"); Thread.sleep(1000); // 模擬工作 semaphore.release(); latch.countDown(); // 完成工作,計(jì)數(shù)減一 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
在這個(gè)例子中,小黑創(chuàng)建了一個(gè)包含5個(gè)線程的場(chǎng)景。使用Semaphore來(lái)控制同時(shí)工作的線程數(shù)量,同時(shí)使用CountDownLatch來(lái)確保所有線程都完成工作后主線程才繼續(xù)執(zhí)行。
第8章:總結(jié)
- 基本概念:Semaphore是一種基于計(jì)數(shù)的同步工具,用于控制同時(shí)訪問(wèn)特定資源的線程數(shù)量。
- 原理理解:Semaphore的實(shí)現(xiàn)依賴(lài)于AQS(AbstractQueuedSynchronizer),提供了一種機(jī)制來(lái)管理和控制線程的訪問(wèn)。
- 實(shí)際應(yīng)用:從資源池管理到限流控制,Semaphore在多種場(chǎng)景中都非常有用。
- 高級(jí)特性:包括公平性和非公平性的選擇,以及對(duì)線程中斷的響應(yīng)。
- 問(wèn)題解決:面對(duì)資源耗盡和性能問(wèn)題,咱們學(xué)習(xí)了如何妥善處理Semaphore帶來(lái)的挑戰(zhàn)。
- 與其他工具結(jié)合:Semaphore能與CountDownLatch、CyclicBarrier等并發(fā)工具結(jié)合使用,解決更復(fù)雜的并發(fā)問(wèn)題。
到此這篇關(guān)于詳解Java中信號(hào)量Semaphore的使用的文章就介紹到這了,更多相關(guān)Java信號(hào)量Semaphore內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中的ArrayList.trimToSize()方法詳解
這篇文章主要介紹了Java中的ArrayList.trimToSize()方法詳解,前幾天看了Java?ArrayList,沒(méi)有明白trimToSize()這個(gè)方法是什么意思,所以看了一下源碼并且debug一下自己的一個(gè)例子,明白了其中的含義,需要的朋友可以參考下2023-11-11Java使用kafka發(fā)送和生產(chǎn)消息的示例
本篇文章主要介紹了Java使用kafka發(fā)送和生產(chǎn)消息的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04詳解elasticsearch實(shí)現(xiàn)基于拼音搜索
這篇文章主要為大家介紹了詳解elasticsearch實(shí)現(xiàn)基于拼音搜索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01淺談@mapper引入不到引入的是@MapperScan的問(wèn)題
這篇文章主要介紹了淺談@mapper引入不到引入的是@MapperScan的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10SpringBoot+Vue中的Token續(xù)簽機(jī)制
本文主要介紹了SpringBoot+Vue中的Token續(xù)簽機(jī)制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06