并發編程系列-Semaphore

Semaphore,如今通常被翻譯為"信號量",過去也曾被翻譯為"信號燈",因為類似于現實生活中的紅綠燈,車輛是否能通行取決于是否是綠燈。同樣,在編程世界中,線程是否能執行取決于信號量是否允許。

信號量是由著名的計算機科學家迪杰斯特拉(Dijkstra)于1965年提出的,直到1980年管程被提出,它一直是并發編程領域的主導方法。如今幾乎所有支持并發編程的語言都支持信號量機制,因此掌握信號量仍然非常必要。

下面我們首先介紹信號量模型,然后介紹如何運用信號量,最后使用信號量來實現一個流量控制器。

信號量模型

信號量模型還是很簡單的,可以簡單概括為: 一個計數器,一個等待隊列,三個方法。在信號量模型里,計數器和等待隊列對外是透明的,所以只能通過信號量模型提供的三個方法來訪問它們,這三個方法分別是:init()、down()和up()。你可以結合下圖來形象化地理解。

alt

這三個操作的具體意義如下所示。

  • 初始化(init()):設定計數器的起始值。
  • 減少(down()):將計數器的值減1;如果此時計數器的值小于0,則當前線程會被阻塞,否則當前線程可以繼續執行。
  • 增加(up()):將計數器的值加1;如果此時計數器的值小于或等于0,則喚醒等待隊列中的一個線程,并將其從等待隊列中移除。

上述提到的init()、down()和up()三個操作都是原子的,并且這種原子性由信號量模型的實現方保證。在Java SDK中,信號量模型由java.util.concurrent.Semaphore類實現,Semaphore類能夠確保這三個操作都是原子操作。

如果你覺得上面的描述有些復雜,可以參考下面的代碼來理解信號量模型的實現。

class?Semaphore{
??//?計數器
??int?count;
??//?等待隊列
??Queue?queue;
??//?初始化操作
??Semaphore(int?c){
????this.count=c;
??}
??//
??void?down(){
????this.count--;
????if(this.count<0){
??????//將當前線程插入等待隊列
??????//阻塞當前線程
????}
??}
??void?up(){
????this.count++;
????if(this.count<=0)?{
??????//移除等待隊列中的某個線程T
??????//喚醒線程T
????}
??}
}

這里再插一句,信號量模型里面,down()、up()這兩個操作歷史上最早稱為P操作和V操作,所以信號量模型也被稱為PV原語。另外,還有些人喜歡用semWait()和semSignal()來稱呼它們,雖然叫法不同,但是語義都是相同的。在Java SDK并發包里,down()和up()對應的則是acquire()和release()。

如何使用信號量

通過前文,你應該會發現信號量的模型還是非常簡單的,那具體應該如何使用呢?其實你可以想象一下紅綠燈。在十字路口,紅綠燈可以控制交通流量,這得益于一個重要的規則:車輛在通過路口前必須檢查是否為綠燈,只有綠燈才能通行。這個規則與我們之前提到的鎖規則很相似,不是嗎?

實際上,信號量的使用也是類似的。讓我們繼續用累加器的例子來說明信號量的使用吧。在累加器的例子中,count += 1操作是一個臨界區,只允許一個線程執行,也就是說需要保證互斥性。那么,在這種情況下,如何使用信號量來控制呢?

實際上非常簡單,就像我們使用互斥鎖一樣,只需要在進入臨界區之前執行一次down()操作,在退出臨界區之前執行一次up()操作就可以了。下面是Java代碼的示例,acquire()就是信號量中的down()操作,release()就是信號量中的up()操作。

static?int?count;
//初始化信號量
static?final?Semaphore?s
????=?new?Semaphore(1);
//用信號量保證互斥
static?void?addOne()?{
??s.acquire();
??try?{
????count+=1;
??}?finally?{
????s.release();
??}
}

接下來我們再來分析一下,信號量是如何確保互斥性的。假設有兩個線程T1和T2同時訪問addOne()方法,當它們同時調用acquire()時,由于acquire()是一個原子操作,因此只能有一個線程(假設是T1)將信號量中的計數器減為0,而另一個線程(T2)將計數器減為-1。對于線程T1來說,信號量中計數器的值是0,大于等于0,所以線程T1會繼續執行;對于線程T2來說,信號量中計數器的值是-1,小于0,根據信號量模型中對down()操作的描述,線程T2將被阻塞。因此,此時只有線程T1可以進入臨界區并執行 count += 1;

當線程T1執行release()操作,也就是up()操作時,信號量中計數器的值是-1,經過加1后的值是0,小于等于0,根據信號量模型中對up()操作的描述,此時等待隊列中的T2將被喚醒。于是,在T1執行完臨界區代碼之后,T2才有機會進入臨界區執行,從而確保了互斥性。

快速實現一個限流器

上述的示例,我們通過使用信號量實現了一個簡單的互斥鎖功能。也許你會覺得奇怪,既然Java的SDK中已經提供了Lock,為什么還需要提供Semaphore呢?實際上,互斥鎖只是Semaphore的一部分功能,而Semaphore還有一個Lock無法實現的功能,那就是:允許多個線程訪問臨界區

實際情況中確實存在這樣的需求。比較常見的例子是我們在工作中遇到的各種資源池,比如連接池、對象池、線程池等等。其中,你可能對數據庫連接池最為熟悉,在同一時刻,允許多個線程同時使用連接池,但每個連接在釋放之前不允許其他線程使用。

實際上,不久前我在工作中也遇到了一個對象池的需求。對象池指的是一次性創建N個對象,然后所有線程重復利用這些對象,當然在對象釋放之前不允許其他線程使用。對象池可以使用List來保存實例對象,這很簡單。但關鍵在于限流器的設計,這里的限流指的是不允許超過N個線程同時進入臨界區。那么如何快速實現這樣的限流器呢?我立刻想到了使用信號量的解決方案。

在上面的例子中,信號量的計數器被設置為1,這個1表示只允許一個線程進入臨界區。但是,如果我們將計數器的值設為對象池中對象的數量N,就可以完美解決對象池的限流問題了。下面是一個對象池的示例代碼。

class?ObjPool<T,?R>?{
??final?List<T>?pool;
??//?用信號量實現限流器
??final?Semaphore?sem;
??//?構造函數
??ObjPool(int?size,?T?t){
????pool?=?new?Vector<T>(){};
????for(int?i=0;?i<size;?i++){
??????pool.add(t);
????}
????sem?=?new?Semaphore(size);
??}
??//?利用對象池的對象,調用func
??R?exec(Function<T,R>?func)?{
????T?t?=?null;
????sem.acquire();
????try?{
??????t?=?pool.remove(0);
??????return?func.apply(t);
????}?finally?{
??????pool.add(t);
??????sem.release();
????}
??}
}
//?創建對象池
ObjPool<Long,?String>?pool?=
??new?ObjPool<Long,?String>(10,?2);
//?通過對象池獲取t,之后執行
pool.exec(t?->?{
????System.out.println(t);
????return?t.toString();
});

我們使用一個List來存儲對象實例,并使用Semaphore實現限流器。關鍵代碼位于ObjPool的exec()方法中,這個方法實現了限流的功能。在這個方法中,我們首先調用acquire()方法(在finally塊中會調用release()方法),假設對象池大小為10,信號量的計數器初始化為10,那么前10個線程調用acquire()方法后可以繼續執行,相當于通過了信號燈,而其他線程將阻塞在acquire()方法上。通過了信號燈的線程,我們為每個線程分配一個對象t(通過pool.remove(0)實現),并執行一個回調函數func,該回調函數的參數正是之前分配的對象t;在執行完回調函數后,它們會釋放對象(通過pool.add(t)實現),同時調用release()方法來更新信號量的計數器。如果此時信號量的計數器值小于等于0,說明有線程在等待,此時會自動喚醒等待的線程。

簡而言之,使用信號量,我們可以輕松實現一個限流器,且使用起來非常簡單。

總結

信號量在Java語言中的知名度相對較低,但在其他編程語言中卻非常有名。Java在并發編程領域取得了很快的發展,重點支持的是管程模型。管程模型在理論上解決了信號量模型的一些不足之處,主要體現在易用性和工程化方面。例如,使用信號量來解決我們前面提到的阻塞隊列問題比使用管程模型要復雜許多。如果你感興趣的話,可以進行了解和嘗試。

本文由 mdnice 多平臺發布

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/40918.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40918.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40918.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

8.10 用redis實現緩存功能和Spring Cache

什么是緩存? 緩存(Cache), 就是數據交換的緩沖區,俗稱的緩存就是緩沖區內的數據,一般從數據庫中獲取,存儲于本地代碼。 通過Redis來緩存數據&#xff0c;減少數據庫查詢操作; 邏輯 每個分類的菜品保存一份緩存數據 數據庫菜品數據有變更時清理緩存數據 如何將商品數據緩存起…

p-級數的上界(Upper bound of p-series)

積分判別法-The Integral Test https://math.stackexchange.com/questions/2858067/upper-bound-of-p-series https://courses.lumenlearning.com/calculus2/chapter/the-p-series-and-estimating-series-value/ 兩個重要級數&#xff08;p級數和幾何級數&#xff09; ht…

WPF顯示初始界面--SplashScreen

WPF顯示初始界面–SplashScreen 前言 WPF應用程序的運行速度快&#xff0c;但并不能在瞬間啟動。當第一次啟動應用程序時&#xff0c;會有一些延遲&#xff0c;因為公共語言運行時&#xff08;CLR&#xff09;首先需要初始化.NET環境&#xff0c;然后啟動應用程序。 對于WPF中…

高憶管理:股票T+0交易是什么意思?t+0交易有什么好處?

股票的買賣準則有很多種&#xff0c;T0買賣便是其中之一。那么股票T0買賣是什么意思&#xff1f;t0買賣有什么優點&#xff1f;高憶管理也為大家預備了相關內容&#xff0c;以供參考。 股票T0買賣是什么意思&#xff1f; T0買賣準則是指出資者當天買入的股票能夠在當天賣出&am…

IP 多播協議(IP Multicast Protocol)

IP 多播協議&#xff08;IP Multicast Protocol&#xff09;是一種在網絡中一對多傳輸數據的通信方式。在傳統的單播通信中&#xff0c;數據從一個發送方發送到一個接收方&#xff1b;而在多播通信中&#xff0c;數據可以從一個發送方傳輸到多個接收方&#xff0c;從而有效地節…

SpringBoot 異步、郵件任務

異步任務 創建一個Hello項目 創建一個類AsyncService 異步處理還是非常常用的&#xff0c;比如我們在網站上發送郵件&#xff0c;后臺會去發送郵件&#xff0c;此時前臺會造成響應不動&#xff0c;直到郵件發送完畢&#xff0c;響應才會成功&#xff0c;所以我們一般會采用多線…

神經網絡基礎-神經網絡補充概念-03-邏輯回歸損失函數

概念 邏輯回歸使用的損失函數通常是"對數損失"&#xff08;也稱為"交叉熵損失"&#xff09;或"邏輯損失"。這些損失函數在訓練過程中用于衡量模型預測與實際標簽之間的差異&#xff0c;從而幫助模型逐步調整權重參數&#xff0c;以更好地擬合數…

指靜脈開集測試(OpenSet-test)代碼(包含7個數據集)

七個數據集:sdu、mmc、hkpu、scut、utfvp、vera、nupt 一、SDU 80%用于訓練,20%用于作為開集測試 1.數據集分割代碼 ①先把636個類別提取出來 func: 創建temp_sdu,將636個類劃分出來。下一個代碼塊將進行openset_sdu的分割import os from shutil import copy, rmtre…

c++--SLT六大組件之間的關系

1.SLT六大組件&#xff1a; 容器&#xff0c;迭代器&#xff0c;算法&#xff0c;仿函數&#xff0c;適配器&#xff0c;空間配置器 2.六大組件之間的關系 容器&#xff1a;容器是STL最基礎的組件&#xff0c;沒有容器&#xff0c;就沒有數據&#xff0c;容器的作用就是用來存…

IO流 詳細介紹

一、IO流概述 1.IO&#xff1a;輸入(Input讀取數據)/輸出(Output寫數據) 2.流&#xff1a;是一種抽象概念&#xff0c;是對數據傳輸的總稱,也就是說數據在設備間的傳輸稱為流&#xff0c;流的本質是數據傳輸IO流就是用來處理設備間數據傳輸問題的。 3.常見的應用&#xff1a…

【Sklearn】基于隨機森林算法的數據分類預測(Excel可直接替換數據)

【Sklearn】基于隨機森林算法的數據分類預測(Excel可直接替換數據) 1.模型原理1.1 模型原理1.2 數學模型2.模型參數3.文件結構4.Excel數據5.下載地址6.完整代碼7.運行結果1.模型原理 隨機森林(Random Forest)是一種集成學習方法,通過組合多個決策樹來構建強大的分類或回歸…

JVM - 垃圾回收機制

JVM的垃圾回收機制(簡稱GC) JVM的垃圾回收機制非常強大&#xff0c;是JVM的一個很重要的功能&#xff0c;而且這也是跟對象實例息息相關的&#xff0c;如果對象實例不用了要怎么清除呢&#xff1f; 如何判斷對象已經沒用了 當JVM認為一個對像已經沒用了&#xff0c;就會把這個…

初識Sentinel

目錄 1.解決雪崩的方式有4種&#xff1a; 1.1.2超時處理&#xff1a; 1.1.3倉壁模式 1.1.4.斷路器 1.1.5.限流 1.1.6.總結 1.2.服務保護技術對比 1.3.Sentinel介紹和安裝 1.3.1.初識Sentinel 1.3.2.安裝Sentinel 1.4.微服務整合Sentinel 2.流量控制 2.1.簇點鏈路 …

Ubuntu中怎么清空mysql數據

要清空 MySQL 數據&#xff0c;可以使用以下步驟來執行。請注意&#xff0c;這將會永久刪除數據庫中的所有數據&#xff0c;請謹慎操作&#xff0c;并在操作前備份重要數據。 登錄 MySQL&#xff1a; 打開終端&#xff0c;使用以下命令登錄到 MySQL 數據庫。根據情況&#xf…

黑馬項目一階段面試58題 Web14題(一)

一、什么是AJAX 異步的JavaScript和XML。用來做前端和后端的異步請求的技術。 異步請求&#xff1a;只更新部分前端界面的請求&#xff0c;做到局部更新。 比如注冊&#xff0c;提示用戶名已存在而整個頁面沒有動 比如百度圖片搜索美女&#xff0c;進度條越變越短&#xff…

== 和 equals 的對比 [面試題]

和 equals 的對比[面試題] 文章目錄 和 equals 的對比[面試題]1. 和 equals 簡介2. Object 類中 equals() 源碼3. String 類中 equals() 源碼4. Integer 類中 equals() 源碼5. 如何重寫 equals 方法 1. 和 equals 簡介 是一個比較運算符 &#xff1a;既可以判斷基本數據類型…

【數據結構OJ題】鏈表的回文結構

原題鏈接&#xff1a;https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId49&&tqId29370&rp1&ru/activity/oj&qru/ta/2016test/question-ranking 目錄 1. 題目描述 2. 思路分析 3. 代碼實現 1. 題目描述 2. 思路分析 在做這道…

re中的match和search有什么區別?

問題:請說明以下re模塊中的match和search有什么區別? re.match()與re.search()的區別 re.match()只匹配字符串的開始,如果字符串開始不符合正則表達式,則匹配失敗,結果返回None,而re.search()匹配整個字符串,直到找到一個匹配 re.search() re.search()掃描整個字符串并…

老師如何制作二維碼分班查詢系統?技術老師分享的創建框架值得借鑒

作為一名班主任&#xff0c;開學前需要搞定分班問題&#xff0c;可以通過制作一個分班二維碼查詢系統&#xff0c;讓學生和家長可以通過掃描二維碼快速查到自己的分班信息&#xff0c;分享一下我制作的過程&#xff0c;希望對老師們有幫助&#xff08;結尾有驚喜&#xff09;&a…

內網穿透——使用Windows自帶的網站程序建立網站

文章目錄 1.前言2.Windows網頁設置2.1 Windows IIS功能設置2.2 IIS網頁訪問測試 3. Cpolar內網穿透3.1 下載安裝Cpolar3.2 Cpolar云端設置3.3 Cpolar本地設置 4.公網訪問測試5.結語 1.前言 在網上各種教程和介紹中&#xff0c;搭建網頁都會借助各種軟件的幫助&#xff0c;比如…