大家好,我是小黑,一個在互聯網茍且偷生的農民工,
先問大家一個問題,在主執行緒中創建多個執行緒,在這多個執行緒被啟動之后,主執行緒需要等子執行緒執行完之后才能接著執行自己的代碼,應該怎么實作呢?
Thread.join()
看過我 并發編程之:執行緒 的朋友應該知道怎么做,在Thread類中有一個方法join(),這個方法是一個阻塞方法,當前執行緒會等待調動join()方法的執行緒死亡之后再繼續執行,

我們通過代碼來看看執行結果,
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run ~");
});
t.start();
t.join();
}
System.out.println("main執行緒執行結束");
}
}

從結果可以看出,main執行緒要等到所有子執行緒都執行完之后才會繼續執行,并且每一個子執行緒是按順序執行的,
我們在來看一下join()方法是如何讓主執行緒阻塞的呢?來看一下原始碼,
public final void join() throws InterruptedException {
// 默認傳入0毫秒
join(0);
}
// 本方法是synchronized的
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 測驗當前執行緒是否還活著
while (isAlive()) {
// 執行wait,當前執行緒等待
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
從join方法的原始碼中我們可以看到幾個重要的資訊,首先join()方法默認是等待0毫秒;join(long millis)方法是一個synchronized方法;回圈判斷當前執行緒是否還活著,什么意思呢?
- main執行緒在呼叫執行緒T的join()方法時,會先獲取T物件的鎖;
- 在join方法中會呼叫T物件的wait()方法等待,而wait()方法會釋放T物件的鎖,并且main執行緒在執行完wait()之后會進入阻塞狀態;
- 最后main執行緒在被notify喚醒之后,需要再回圈判斷T物件是否還活著,如果還活著會再次執行wait(),
而在執行緒執行完run()方法之后,JVM會呼叫該執行緒的exit()方法,通過notifyAll()喚醒處于等待狀態的執行緒,
private void exit() {
if (group != null) {
// 終止group中的執行緒this
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
// 喚醒等待執行緒
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
細心的話你會發現,使用Thread.join()只能做到讓一個執行緒執行完之后,做不到同時等待多個執行緒,比如我們上面的代碼,執行緒1執行完之后才能執行執行緒2,無法做到讓執行緒1和執行緒2同時處理,
CountDownLatch
而在JUC包中的工具類CountDownLatch具備和Thread.join()方法同樣的能力,可以等待一個執行緒執行完之后再處理,并且支持同時等待多個執行緒,我們來修改一下上面Thread.join()的例子,
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " run ~");
countDownLatch.countDown();
});
t.start();
}
countDownLatch.await();
System.out.println("main執行緒執行結束");
}
}

CountDownLatch需要在創建時指定一個計數值,在子執行緒中執行完之后呼叫countDown()方法進行遞減,主執行緒的await()方法會等到值減為0之后繼續執行,
從運行結果我們可以看到,100個子執行緒并不是按順序執行的,而是隨機的,
我們通過CountDownLatch的原始碼來看一下是如何實作的,
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
在CountDownLatch中我們看到有一個Sync變數,從上一期AQS原始碼決議內容中我們知道Sync是AQS的一個子類實作;
首先構造方法傳入的count值會作為引數賦值給Sync中的state變數,
然后我們來看一下在執行緒中的CountDownLath.countDown()方法會做些什么事情,
public void countDown() {
// 釋放共享鎖
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
如果有看我上期AQS原始碼決議的同學一定很熟悉,這段代碼就是共享鎖的解鎖程序,本質上就是對state-1,
那么主執行緒是如何實作的等待呢?我們猜一下,應該是去判斷state有沒有減為0,如果減為0則代表所有的執行緒都執行完countDown()方法,則可以繼續執行,如果state還不等于0,則表示還有執行緒正在執行,等待就OK啦,
我們來看看原始碼,是否和我們猜想的一樣呢?
public void await() throws InterruptedException {
// 可中斷地獲取共享鎖
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 嘗試獲取共享鎖
if (tryAcquireShared(arg) < 0)
// state還不是1
doAcquireSharedInterruptibly(arg);
}
// 獲取鎖狀態,當state減為0時,回傳1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
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;
}
}
// 執行緒在這里park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以發現await()方法和我們昨天看到的共享鎖解鎖程序一模一樣,符合我們的猜想,
所以,CountDownLatch的底層實作也是依靠AQS來完成的,現在大家肯定對于AQS有更深刻的認識了,
區別
我們現在來對比一下Thread.join()和CountDownLatch有哪些區別:
- Thread.join()是Thread類的一個方法,而CountDownLatch是JUC包中的一個工具類;
- Thread.join()的實作是依靠Object的wait()和notifyAll()來完成的,而CountDownLatch是通過AQS完成的;
- Thread.join()只支持讓一個執行緒等待,不支持同時等待多個執行緒,而CountDownLatch可以支持,所以CountDownLatch的效率要更高,
好的,本期內容就到這里,我們下期見,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/297984.html
標籤:其他
