Java併發程式設計系列 CountDownLatch

2021-08-08 20:14:52 字數 3598 閱讀 9140

countdownlatch簡介

countdownlatch是基於aqs實現的乙個執行緒同步工具,也稱為閉鎖。它的作用是讓乙個或者多個執行緒等待某個事件的發生。簡單的理解為countdownlatch有乙個正數計數器,countdown方法對計數器做減操作,await方法等待計數器達到0。所有await的執行緒都會阻塞直到計數器為0或者等待執行緒中斷或者超時。

countdownlatch唯一的構造方法countdownlatch(int count),當在閉鎖上呼叫countdown()方法時,閉鎖的計數器將減1,當閉鎖計數器為0時,閉鎖將開啟,所有等待的執行緒將通過閉鎖開始執行。

下面例子是讓兩個執行緒等待另外兩個執行緒。

public

class

work

implements

runnable

public

void

run(

)catch

(interruptedexception e)

system.out.

println

(this

.name +

" done!");

this

.downlatch.

countdown()

;}public

static

void

main

(string[

] args)

throws interruptedexception

catch

(interruptedexception e)

system.out.

println

("waiting over!");

}).start()

; latch.

await()

; system.out.

println

("all done");

}}

實現原理

下面分析countdownlatch的實現原理,先從它的構造方法入手。

public

countdownlatch

(int count)

sync繼承了aqs,aqs中的state在這裡表示計數值得大小,即等待幾個執行緒的意思。

private

static

final

class

sync

extends

abstractqueuedsynchronizer

intgetcount()

}

await

在前面的例子中,我們知道了 await 的作用是等待計數器減為0,否則一直阻塞,直到超時或者中斷。這裡我們分析無參的方法。

public

void

await()

throws interruptedexception

// aqs

public

final

void

acquiresharedinterruptibly

(int arg)

throws interruptedexception

protected

inttryacquireshared

(int acquires)

如果執行緒已經中斷,則丟擲異常,否則,去獲取鎖,也就是判斷state(計數器)是否為0。如果不為0,那麼進入doacquiresharedinterruptibly方法去掛起執行緒。

private

void

doacquiresharedinterruptibly

(int arg)

throws interruptedexception }if

(shouldparkafte***iledacquire

(p, node)

&&parkandcheckinterrupt()

)throw

newinterruptedexception()

;}}finally

}

這段**和我們在aqs中分析acquirequeued方法邏輯基本是一樣的,多了一步建立node節點,並且node是定義成共享的node.shared,並且加入到同步佇列中。被喚醒後重新嘗試獲取鎖,不只設定自己為head,還需要通知其他等待的執行緒。嘗試去獲取鎖,如果返回值大於0,表示獲取成功,則去喚醒後面的節點。

private

void

setheadandpropagate

(node node,

int propagate)

}

sethead設定頭節點後,取出下乙個節點,如果也是共享型別,進行doreleaseshared釋放操作。下個節點被喚醒後,重複上面的步驟,達到共享狀態向後傳播。

countdown

countdown是釋放鎖的過程,releaseshared在aqs中實現,tryreleaseshared是由countdownlatch內部類sync實現。

public

void

countdown()

public

final

boolean

releaseshared

(int arg)

return

false

;}

嘗試釋放鎖,如果完全釋放通知喚醒佇列中的執行緒,完全釋放指的是state減為0,同步佇列中儲存的是呼叫了await方法的執行緒。

private

void

doreleaseshared()

else

if(ws ==0&&

!compareandsetwaitstatus

(h,0

, node.propagate)

)continue

;// loop on failed cas}if

(h == head)

// loop if head changed

break;}

}

如果頭結點的狀態為signal,通過cas置位0,並呼叫unparksuccessor喚醒下個節點。被喚醒的節點狀態會重置成0,在下一次迴圈中被設定成propagate狀態,代表狀態要向後傳播。此方法的出口只有乙個,即 h == head條件,當同步佇列中沒有可以喚醒的節點時,此條件成立。

private

void

unparksuccessor

(node node)

if(s != null)

locksupport.

unpark

(s.thread)

;}

這裡需要注意在aqs中,我們分析到head節點是持有鎖的執行緒,在此處,head節點是沒有執行緒與它對應,head節點後面的都是阻塞執行緒。unparksuccessor方法的作用是尋找下乙個可用節點(status<0),將它的狀態置位0,並且喚醒。

Java併發程式設計系列 CyclicBarrier

cyclicbarrier簡介 cyclicbarrier也是基於reentrantlock和condition的乙個同步工具類,它的作用是讓一些執行緒到達某個公共屏障點時,等待未到達的執行緒。當所有執行緒到達屏障點時,繼續往下執行。先看乙個例子 public class cyclicbarrier...

Java的高併發程式設計系列(三)

鎖定某物件o,如果o的屬性發生改變,不影響鎖的使用,但是如果o變成另外乙個物件,則鎖定的物件發生改變,應該避免將鎖定物件的引用變成另外乙個物件。public class demo17 catch interruptedexception e system.out.println thread.cur...

JAVA併發程式設計

通過常量字串 string 來呼叫 wait 或 notify 方法所導致的問題是,jvm 編譯器會在內部自動將內容相同的 string 轉變為相同的物件。這意味著,即便你建立了兩個不同的 mywaitnotify 例項,他們內部的 mymonitorobject 變數也會指向相同的 string ...