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