程序猿學社的GitHub,歡迎Star
https://github.com/ITfqyd/cxyxs
本文已記錄到github,形成對應專題。
前言
jdk1.5以後,增加了不少內容,我們就來看一看CountDownLatch,實際上,很多的方法,如果實際開發過程中,沒有用到過,我們幾乎都不怎麼熟悉。一切的一切,都感覺十分的陌生。
1.CountDownLatch
概念
允許一個或多個線程等待直到在其他線程執行完畢後再執行。
- CountDownLatch用給定的計數初始化
- await方法阻塞
- 直到countDown()方法的調用而導致當前計數達到零,之後所有等待線程被釋放,並且任何後續的await 調用立即返回。
常用api接口
方法 | 描述 |
---|---|
await() | 導致當前線程等到鎖存器計數到零,除非線程是 interrupted 。線程處於休眠狀態,直到計數爲0 |
countDown() | 減少鎖存器的計數(調用一次,計數減去1),如果計數達到零,釋放所有等待的線程。 |
應用場景
現在是疫情期間,爲了保證員工的安全,減少與他們接觸的機會,某某公司,實行車接車送。所有人員就位後,司機需要向HR彙報,人員上車情況。
實戰
demo案例
package com.cxyxs.thread.thirteen;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Description:轉發請註明來源 程序猿學社 - https://ithub.blog.csdn.net/
* Author: 程序猿學社
* Date: 2020/3/3 16:24
* Modified By:
*/
public class Emp {
//初始化爲3,標識有3個員工需要等待
public static CountDownLatch countDownLatch = new CountDownLatch(3);
/**
* 員工正在上車中
*/
public void toAddress(){
System.out.println(Thread.currentThread().getName()+"正在步行去固定的上車地點...");
//模擬去固定地點上車的過程
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"已上車");
}
}
調用類
package com.cxyxs.thread.thirteen;
/**
* Description:轉發請註明來源 程序猿學社 - https://ithub.blog.csdn.net/
* Author: 程序猿學社
* Date: 2020/3/3 16:22
* Modified By:
*/
public class Demo1 {
public static void main(String[] args) {
Emp emp = new Emp();
//使用lambda方式實現一個線程
new Thread(()->{
emp.toAddress();
},"張三").start();
new Thread(()->{
emp.toAddress();
},"李四").start();
new Thread(()->{
emp.toAddress();
},"王五").start();
new Thread(()->{
try {
emp.countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"向HR彙報上車情況!");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"黃師傅").start();
}
}
- 初始化計數爲3,調用countDown方法後,每次減去1,一直到零,就會主動釋放,繼續await後面的代碼。
- 在實際使用過程中,一定要記得調用一次,就調用countDown方法,不然,線程會一直堵塞在哪裏。
源碼分析
CountDownLatch初始化
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
- 初始化一個CountDownLatch對象,需要給定計數的初始值,如果<0,則會拋出異常。
- 創建了一個Sync對象。
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
protected final void setState(int newState) {
state = newState;
}
}
private volatile int state;
private final Sync sync;
- 首先調用Sync的構造方法。該構造方法會調用setState方法。
- sync繼承AbstractQueuedSynchronizer抽象類,其中有一個變量state,就是爲了同步狀態的。注意,他的前面有volatile關鍵字,表示線程之間是可見的,只要值有變動,其他的項目都會知道。類似於開發過程中,某某提交了代碼,但是,不說明,其他的人,只有更新代碼的時候,發現起衝突才知道,改了同一個文件,而可見性,就是,某人一改代碼,馬上,就全網通知,說我改代碼了,你們都拉取一下最新的代碼。
countDown方法
public void countDown() {
sync.releaseShared(1);
}
- 調用AbstractQueuedSynchronizer方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- 調用了兩個方法tryReleaseShared和doReleaseShared
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
- 獲取state的數量,也就是計數器的值,如果爲0直接返回false。表示計數已經完成。
- compareAndSetState方法我們一步步跟蹤下去,發現調用的compareAndSwapInt被native關鍵字修飾,說明是調用的事c或者c++的方法。利用了CAS,可以理解爲樂觀鎖, 但是,可以保證該變量是原子的。代碼塊,就無法保證了。說到原子,我們就可以聯想起多個線程操作i++線程不安全的問題。這也是一到面試經常會的題目。
- -1完後,再次檢查是否爲0,如果爲0表示計數已完成。
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
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);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
- 在jdk1.8的api文檔中,沒有找到這個方法,但是jdk1.8源碼裏面有這個方法。後續再花時間看看把。
await方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- 實際上是調用父類的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 判斷當前線程是否中斷,如果中斷則拋出InterruptedException異常。
- tryAcquireShared的返回值小於纔會調用doAcquireSharedInterruptibly方法
tryAcquireShared代碼
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
- 判斷state的計算是否爲0,如果爲0返回1.
doAcquireSharedInterruptiblyd源碼
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
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);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 看着也是一頭霧水。
總結:
就一個簡單的方法調用,實際上底層幫我們做了很多的工作,裏面層層的調用,實際上,需要理解的很透徹,還的需要花大量的時間。