業精於勤荒於嬉 行成於思毀於隨
Semaphore訊號量是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。
舉個不適合吃飯時候看的例子:比如現在有五個茅坑,有50個人要蹲坑,那麼最先搶到的五個人先蹲,剩下45個人排對等著,等有人先蹲完出來了,告訴排在第一個的人
開始前建議先去看看我之前AQS的文章,以及很多共享鎖的程式碼在之前CountDownLatch文章裡介紹過了。
看看例子:
public class SemaphoreTrain { static class Worker extends Thread { private int n; private Semaphore semaphore; public Worker(int n, Semaphore semaphore) { this.n = n; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("Worker num " + n + " use machine"); Thread.sleep(2000); System.out.println("Worker num " + n + " stop use"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { int worker = 6; //工人數 int machine = 4; //機器數 Semaphore semaphore = new Semaphore(machine); for (int i = 0; i < worker; i++) { new Worker(i, semaphore).start(); } } }
Worker num 1 use machine
Worker num 3 use machine
Worker num 2 use machine
Worker num 0 use machine
Worker num 0 stop use
Worker num 1 stop use
Worker num 3 stop use
Worker num 2 stop use
Worker num 4 use machine
Worker num 5 use machine
Worker num 4 stop use
Worker num 5 stop use
只允許最多四個執行緒同時執行,其它的等待執行執行緒release退出。
來看看原始碼實現:
本質上於CountDownLatch一樣屬於共享鎖,有非公平與公平兩種模式。
private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1192457210091910933L; Sync(int permits) { setState(permits); } final int getPermits() { return getState(); } // 以上面的例子來說明:非公平鎖的獲取鎖邏輯是先計算出空閒機器數remaining,若其小於0直接返回remaining //若大於等於0,CAS賦值state,返回remaining //根據AQS共享鎖的規則,返回值小於0則代表無法獲取到鎖需要入同步佇列等待。若大於等於0,執行緒不會被阻塞 final int nonfairTryAcquireShared(int acquires) { for (;;) { // 為什麼要將操作放在無限for迴圈裡?迴圈CAS + volatile = 執行緒安全 int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } // 公平與非公平模式的嘗試釋放鎖的操作相同,即這裡的tryReleaseShared // 還以上面例子說明:一個工人用完機器了,他需要做的就是歸還機器,就是CAS更改state值,成功就返回true protected final boolean tryReleaseShared(int releases) { for (;;) { // 這裡為什麼也放在無限for迴圈裡?迴圈CAS + volatile = 執行緒安全 int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } } final void reducePermits(int reductions) { for (;;) { int current = getState(); int next = current - reductions; if (next > current) // underflow throw new Error("Permit count underflow"); if (compareAndSetState(current, next)) return; } } final int drainPermits() { for (;;) { int current = getState(); if (current == 0 || compareAndSetState(current, 0)) return current; } } }
這裡說點我對執行緒安全的思考:同步狀態state在AQS中是volatile修飾的,確保了可見性,迴圈CAS確保了符合操作的原子性,二者結合保證了state操作的執行緒安全性。這就是上面程式碼中兩個問題的答案,以nonfairTryAcquireShared為例,一個執行緒通過getState得到了state的最新值,計算出了remaining大於0,於是CAS賦值,那麼在這些個操作過程中,state很可能已經被改變,CAS會失敗,如何處理?迴圈再來一次唄,再次獲取state最新值進行計算不就可以了。Doug Lea大神在設計JUC框架時採用了這種非鎖式的不排他的方法來確保執行緒安全。
非公平鎖
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
// 實現AQS共享鎖的tryAcquireShared方法,內部呼叫了nonfairTryAcquireShared
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
公平鎖
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
// Semaphore公平模式獲取共享鎖邏輯
protected int tryAcquireShared(int acquires) {
for (;;) {
// 之前文章說過公平就是不允許插隊,實現邏輯就在hasQueuedPredecessors裡。
// 該方法當同步佇列裡有其它等待更久的執行緒就返回true,代表你不被允許執行乖乖後面排對去
if (hasQueuedPredecessors())
return -1; // AQS共享鎖規定tryAcquireShared返回值<0則代表獲取鎖失敗,構成節點放到佇列
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
建構函式
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 布林值選擇模式
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
acquire
獲取鎖操作:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//-----------------------------方法在AQS中
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); // 直接拋異常
//小於零沒有獲得許可,構造節點加入佇列中等待
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // 構造節點加入到佇列。節點為共享節點代表共享鎖
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg); // r表示剩下的空餘位
if (r >= 0) {
setHeadAndPropagate(node, r); // 將當前獲取鎖的節點設定為頭節點,根據情況喚醒後繼執行緒
p.next = null; // help GC
failed = false;
return;
}
}
//這裡shouldParkAfterFailedAcquire會將前面節點的狀態改為Signal
//對於等待佇列中節點的waitStatus初始為0,之後由後加入的節點改為SIGNAL
//SIGNAL這個狀態它表明你有後繼節點,release時喚醒它
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException(); //直接拋異常
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate
// 該方法只在doAcquireShared中呼叫。當同步佇列中head.next節點被喚醒並獲取了共享鎖,它會呼叫該方法,
//目的1,將該節點設為新的頭節點。
//2,嘗試喚醒後繼節點。我麼來分析分析if的這些判斷條件:若引數propagate>0(表明仍有空位可爭)且有後繼的共享節點,則喚醒後繼節點
//or 無空缺被佔滿了,propagate <=0,但h的狀態或是新的頭節點node的狀態waitStatus<0,表明有可能有後繼節點(因為PROPAGATE也<0),
//這種情況下我們也去呼叫喚醒操作。哪怕是h為null,表示我們無法獲取到相關資訊的情況下仍然會去嘗試喚醒node的後繼節點(若其存在的化)。
// 可以看出這種設計是保守的,它儘可能多的去嘗試喚醒等待的執行緒,跟獨佔鎖的操作截然不同,這就造成在併發競爭下可能會發生不必要的喚醒,
//不過這些節點總是需要被喚醒的,不如就在這裡多嘗試。
//對於上面說的waitStatus<0情況下節點狀態為PROPAGATE的情況,該種情況下同樣無法獲知有無後繼節點
//關於PROPAGATE:該狀態只在doReleaseShared中被設定,有執行緒釋放了共享鎖,但由於當時頭節點狀態為0,
//無法獲知有無後繼節點,於是將頭節點狀態改為PROPAGATE),
//那麼此時情況可能已經變了,有了後繼節點,所以我們嘗試去喚醒後繼共享節點
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node); //將node設定成新頭節點
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // doReleaseShared方法喚醒head.next節點的執行緒
}
}
總之setHeadAndPropagate喚醒後繼節點的操作偏於保守,它儘可能多的去喚醒後繼的執行緒。
release
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 表明有後繼節點
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h); // 喚醒後繼節點
}
// PROPAGATE這個標記只在一處用到,即這裡,該方法在同步佇列頭節點狀waitStatus=0
//時將其設為PROPAGATE ,本身doReleaseShared是為了釋放後繼節點的,但是當頭節點狀態為0,我們不知
//道有沒有後繼節點,所以就採用這種方式,將頭節點標記為PROPAGATE,意味著將共享鎖的釋放傳遞下去
//並不會對之後的操作有什麼影響,之後進隊的節點獲取不到鎖就會呼叫
//shouldParkAfterFailedAcquire,該方法會將頭節點改為SIGNAL。
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
總結
Semaphore維持了一個同步狀態state(大小初始化時設定,代表最大允許執行執行緒數),在acquire時將state減1,小於零加入同步佇列等待,大於零則CAS更改值。Semaphore.release happen-before Semaphore.acquire。
Actions prior to "releasing" synchronizer methods such as
Lock.unlock
,Semaphore.release
, andCountDownLatch.countDown
happen-before actions subsequent to a successful "acquiring" method such asLock.lock
,Semaphore.acquire
,Condition.await
, andCountDownLatch.await
on the same synchronizer object in another thread.
相關推薦
業精於勤荒於嬉-行成於思而毀於隨
最近恰逢日程都集中到了一起, 心思有點混亂, 感情有點彌散, 夢裡也竟然思考了很多。 這是正常的吧, 總會有比較的, 人又是貪婪的, 你不能期望你擁有的什麼都是最好的, 要適時降低自己的期望, 人生才會過得幸福。 其實這次的選擇並不差, 只是提前感受到了一
業精於勤荒於嬉 行成於思毀於隨
Semaphore訊號量是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。 舉個不適合吃飯時候看的例子:比如現在有五個茅坑,有50個人要蹲坑,那麼最先搶到的五個人先蹲,剩下45個人排對等著,等有人先蹲完出來了,告訴排在第一個的人 開始
【Kevin's Blog】業精於勤荒於嬉,行成於思毀於隨!
KaliTools Kali Linux遵循Debian開發標準重建,經過之前的BackTrack重寫,擁有其良好血統。預裝了超過300個滲透測試軟體,像資訊蒐集;漏洞分析;無線攻擊;嗅探欺騙;密碼攻擊;維持訪問;漏洞利用;逆向工程;壓力測試;取證工具類等等,比如N
業精於勤毀於嬉,行成於思毀於隨
勤基本能做到了,但是要更勤! 思還沒有做到,現在只做到大量的獲取知識,思考方面做的比較少,需要注意啊!!! 什麼叫做思考? 就是同一件事情,你思考如何處理,別人思考如何處理,再做對比,分析過程和結果,學習別人思考問題的角度和方法,以便提升自我智慧,更好地解決問題。 教育的目
業精於勤,荒於嬉;行成於思,毀於隨。
public class Main { public static final String CONTAINER_KEY = "dubbo.container"; public static final String SHUTDOWN_HOO
業精於勤,荒於嬉
Bootstrap本身帶有分頁元件,以此為基礎延伸一個 JavaScript類,以便更好使用。效果圖如下: 使用上必須先在 html檔中部署 Bootstrap分頁元件,並命名為 #nvaPageUL,如下: <nav aria-label="Page navigation">
沉浸於此,成體系,求自由
分片是分散式儲存的突出特點。 必要性 如果Redis叢集的每個資料庫都儲存叢集中的所有資料,那麼叢集的總資料儲存量受限於可用儲存記憶體最小的資料庫節點,形成木桶效應。由於Redis中的所有資料都基於記憶體儲存,這一問題就尤為突出了,尤其是
超長顯示省略號...,相容各瀏覽器,適用於多行
超長顯示省略號是一個很困擾的事情,各個瀏覽器的對css的解析不同,比如 1. IE可以使用overflow:hidden;white-space:nowrap;text-overflow:ellipsis, 2. Opera中也有相關的支援屬性text-overflow:-
iOS動態行高佈局(區別於傳統計算字數)
iOS動態杭高一般都採用sizeWithFont: constrainedToSize:這種方式來計算出高度 然後返回行高;最近我發現一種新的方式,就是不需要計算的,動態佈局;只需要在tableView裡面,分三個步驟來寫:1:給 estimateRowHeight 賦值,這
Spring Boot 中直接操作 hbase 修改賬戶餘額,實現行級鎖(類似於版本號控制)
應用場景近期開發中遇到 直接修改hbase資料 ,用Phoenix 查詢出來的資料 型別不一致的 問題。因修改的是使用者的賬戶餘額,涉及到錢的問題都不是小問題。初次想法使用tephra事務,但官網說目前還是 Beta版本的,感興趣的可以研究研究。所以考慮直接操作hbase資
sql合併多列成一個字串,多行成一個字串
以這個表為例: sql1: select GROUP_CONCAT(prov_code SEPARATOR ',') AS prov_code from tb_prov_code 可以看出,順序就是表裡面的順序。這裡是按照逗號隔開。 sql2: 想要拼接同一行 select
數字中國行·成渝論壇順利召開, 鷹雲智慧攜手阿里雲助力傳統商圈構築出數字化新場景
做新零售,成都已經成為了各大商業巨頭必爭之地。 當下,成都作為北京、上海、深圳之後,排名第四的零售之城,僅僅在2018年上半年成都實現社會消費品零售總額3326.4億元,在副省級城市零售總額指標增速排位中,成都消費潛力增速升至第1位,其中最亮眼的表現就是在新零售的佈局上。 因此,為了更好的促進新零售與購物
3星|《文明的崩塌:公元前1177年的地中海世界》:青銅時代全球化國際化的地中海地區,最終毀於天災人禍
文明的崩塌:公元前1177年的地中海世界 作者是考古學家。書中綜述考古學的一些發現和學說:三千年前的青銅時代晚期,地中海各國之間出現了頻道的貿易往來,有點像國際化大勢下的當今世界,但是在300年的繁榮之後,一些天災人禍讓這個國際化體系崩潰了,直到數百年後才逐步恢復。 作者坦言這個觀點還有爭議,他也不是
《精益創業》- 天下大事必作於細,天下難事必作於易
《道德經》和《精益創業》的暗合可以顯示古今中外的人類智慧多麼相通。不過,與老子惜字如金的風格不同,Eric Ries從實際操作的角度寫出了一本300頁厚的書,對如何“作於細”和“作於易”給出了系統化的建議。 背景: Eric Ries是一個矽谷的程式開發者,曾參
不能繼承於QObject的類就一定不能使用信號槽?(用一個代理類進行發射就行了)
-c 問題 ges object 編譯過程 報錯 第三方庫 nal 解決 首先不能繼承QObject的情況在開發中遇到得並不多,筆者在一年多的Qt項目開發中只遇到兩三次。而且都是因為引進了第三方庫導致編譯過程中報錯。 要想解決這個問題其實不難,因為筆者遇到的問題都是想定義
ESP8266 是一個完整且自成體系的 WiFi 網絡解決方案,能夠獨立運行,也可以作為從機搭載於其他主機 MCU 運行
處理 天線 -1 系統資源 pwm adc 高度 能夠 能力 ESP8266EX 在搭載應用並作為設備中唯一的應用處理器時,能夠直接從外接閃存中啟動。內置的高速緩沖存儲器有利於提高系統性能,並減少內存需求。另外一種情況是,ESP8266EX 負責無線上網接入承擔 WiF
《Elixir in Action》書評及作者問答錄(作者 Sergio De Simone ,譯者 邵思華 發布於 2015年9月29日)
服務器 編程思想 href 地產 完全 負載 server 後臺 tsa 《Elixir in Action》是由Manning所出版的一本新書,本書為讀者介紹了Elixir這門語言以及Erlang虛擬機,同時也討論了與並發編程、容錯以及與高可用性相關的話題。InfoQ有幸
木秀於林,風必摧之;行高於人,眾必毀之?
不同 三國 行高 問題 pst 自己 www. 微軟雅黑 朋友 前段時間在一本小說裏面看到這句話,心裏感觸頗深,特地在此寫下感想,簡單分析下含義。 這句話原出自三國魏人李康的《運命論》。看到這句話,我想跟多人會為秀木和高人感到惋惜,憐憫之情油然而生。但是在我們感嘆他們的
全網最詳細的IDEA、Eclipse和MyEclipse之間於Java web項目發布到Tomcat上運行成功的對比事宜【博主強烈推薦】【適合普通的還是Maven方式創建的】(圖文詳解)
led 交流 之間 精神 推薦 enter style images java web 不多說,直接上幹貨! IDEA 全網最詳細的IDEA裏如何正確新建
css設定固定高度多行超出變省略號,適用於手機端
基本設定: display: -webkit-box;/** 物件作為伸縮盒子模型顯示 **/ word-break: break-all; text-overflow: ellipsis; -webkit-box-orient: vertical