本文轉載自個人掘金博客:https://juejin.im/post/5ece5f71f265da76de5cda58
基本含義
如果一個線程A執行了thread.join()語句,其含義是:當前線程A等待thread線程終止之後才從thread.join()返回。
線程Thread除了提供join()方法之外,還提供了join(long millis)和join(long millis,int nanos)兩個具備超時特性的方法。這兩個超時方法表示,如果線程thread在給定的超時時間裏沒有終止,那麼將會從該超時方法中返回。
實現原理
首先介紹下線程的狀態
線程的狀態
Java線程在運行的生命週期中可能處於6種不同的狀態,在給定的一個時刻,線程只能處於其中的一個狀態。如下內容截取JDK 1.8 Thread.java的源碼:
- NEW: 初始轉態,線程被構建,但是還沒有調用start()方法。
- RUNNABLE: 正在執行的線程狀態,JVM中runnable線程狀態對應於操作系統中的就緒和運行兩種狀態。
- BLOCKED: 線程等待monitor互斥量的阻塞狀態,在blocked狀態的線程正在被執行Object.wait()後等着進入或者再次同步塊或者同步方法。
- WAITING: 等待狀態,下列方法會導致線程處於等待狀態:
- Object.wait with no timeout
- Thread.join with on timeout
- LockSupport.park
- TIMED_WAITING: 超時等待,超過等待時間便會自動返回運行狀態,下列方法會導致線程處於超時等待狀態:
- Thread.sleep
- Object.wait(long) with timeout
- Thread.join(long) with timeout
- LockSupport.parkNanos
- LockSupport.parkUntil
- TERMINATED: 線程完成執行後結束的狀態。
在介紹下Monitor
Monitor
Monitor是 Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象的鎖。每一個對象都有,也僅有一個 monitor。
在HotSpot JVM中,monitor是由ObjectMonitor實現的,其主要數據結構如下(位於HotSpot虛擬機源碼ObjectMonitor.hpp文件,C++實現的):
ObjectMonitor() {
_header = NULL;
_count = 0; //記錄個數
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //處於wait狀態的線程,會被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處於等block狀態的線程,會被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中主要有以下4個參數:
- _Owner: 用於指向ObjectMonito對象的線程
- _EntrySet:用來保存處於blocked狀態的線程列表
- _WaitSet: 用來保存處於waiting狀態的線程
- _count: 計數器
當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合,當線程獲取到對象的monitor 後進入 _Owner 區域並把monitor中的owner變量設置爲當前線程。同時monitor中的計數器count加1,若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復爲null,count自減1,同時該線程進入 _WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor(鎖)。如下圖所示:
實現機制
一個簡單的例子。
public class ThreadA {
public static void main(String[] args) {
Runnable r = () -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("子線程執行完畢");
};
Thread threadB = new Thread(r, "Son-Thread");
//啓動線程
threadB.start();
try {
//調用join()方法
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程執行完畢");
System.out.println("~~~~~~~~~~~~~~~");
}
}
底層是如何實現join()語義的呢,以上面的例子舉例。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
...
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
...
if (g == null) {
g = parent.getThreadGroup();
}
...
}
...
public synchronized void start() {
...
group.add(this);
...
}
- 由於join(long millis)方法加了對象鎖,鎖的是Thread類當前對象實例即threadB。同時,Thread.start()方法在啓動後,threadB也持有自己線程對象實例的所有內容,包括對象實例threadB的對象鎖。具體可參見start0()源碼。
public final void join() throws InterruptedException {
join(0);
}
...
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
...
}
- 如果threadB線程在join()方法前執行完了,釋放了對象鎖,threadA獲取鎖進入同步方法join(long millis)時,調用threadB的方法isAlive()判斷threadB線程已經不存活,那麼執行完join()邏輯退出,繼續執行threadA的邏輯。
Object.java
/**
* The current thread must own this object's monitor. Causes the current thread to wait until either another thread invokes the method...
* This method causes the current thread call it to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.
*/
public final native void wait(long timeout) throws InterruptedException;
- 如果threadB線程在join()方法前沒執行完,並且由於某種原因釋放了對象鎖,當threadA獲取鎖進入同步方法join(long millis)時,調用threadB的方法isAlive()判斷threadB線程還存活。於是,threadA就調用native方法wait()釋放鎖並進行等待(threadA進入threadB對應的monitor對象的Wait Set,此時threadA的線程狀態爲waiting)。以便這個對象鎖能被threadB獲取繼續執行。直到threadB執行完成,釋放鎖並結束。
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
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;
}
ThreadGroup.java
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
...
}
}
- threadB線程結束時會執行exit()方法,進行一些資源的清理。從源碼的註釋可以發現,這個時候實際上線程是事實上存在的。那麼是誰喚醒waiting的threadA呢?
錯誤解釋:有很多博文的大致解釋如下:threadB線程結束時會執行exit()方法,notifyAll()同一線程組的其他線程。threadA線程在new threadB的時候,threadA和threadB共享一個線程組。同時線程初始化的時候,線程所在的線程組都包含線程本身,於是threadB的線程組會包含threadA。那麼,threadB結束時threadA會被notify。
這個解釋是錯誤的,爲什麼呢?由於if (nthreads == 0)的觸發條件不滿足,threadA和threadB共享一個線程組,當threadB被移除了,threadA還在線程組中,nthreads = 1。
/jdk7/hotspot/src/os/linux/vm/os_linux.cpp
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
static void *java_start(Thread *thread) {
...
thread->run();
return 0;
}
/jdk7/hotspot/src/share/vm/runtime/thread.cpp
void JavaThread::run() {
...
thread_main_inner();
}
void JavaThread::thread_main_inner() {
...
this->exit(false);
delete this;
}
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
...
}
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
正確解釋:在線程native代碼的run()方法的結束,native代碼會將線程的alive狀態置爲false,同時會notifyAll等待在這個線程實例上的所有其他線程。根據上面的c++源碼,是lock.notify_all(thread) 這個動作會notify所有等待在當前線程實例上的其他線程。
-
那麼,threadB結束時threadA會被notify,從而threadB對應的monitor對象的Wait Set移動到該monitor對象的Entry Set,線程狀態變爲Blocked,等待調度獲取monitor的控制權。
-
threadA獲取monitor的控制權後,繼續執行while (isAlive()) 循環,此時isAlive()爲false。那麼執行完join()邏輯退出,繼續執行threadA的邏輯。
通過綜上的設計,Thread.join()實現了當前線程A等待thread線程終止之後才從thread.join()返回的設計邏輯。
Debug分析
我們通過上面的那個簡單的例子來Debug逐點分析:
- 當主線程執行到join()邏輯中時,是RUNNING的狀態
- 當子線程執行到exit()邏輯時,threadB依舊是存活,狀態爲RUNNING
threadA的狀態爲WAIT
- threadB執行到threadTerminated()邏輯,這時候發現nthreads:1,根本不會執行notifyAll()操作。就算執行了notifyAll()操作,鎖的對象都不一樣。一個是threadB的實例,一個是線程組的實例。
等待/通知的經典範式
可以發現Thread.join()方法與等待/通知的經典範式中的等待範式如出一轍。 而Thread.exit()方法則有點類似於其中的通知範式。
等待/通知的經典範式分爲兩個部分:等待方和通知方。 等待方遵循如下原則:
- 獲取對象的鎖。
- 如果條件不滿足,那麼調用對象的wait()方法,被通知後仍要檢查條件。
- 條件滿足則執行對應的邏輯。 對應的僞代碼如下:
synchronized(對象) {
while(條件不滿足) {
對象.wait();
}
對應的處理邏輯
}
通知方遵循如下原則:
- 獲得對象的鎖。
- 改變條件。
- 通知所有等待在對象上的線程。 對應的僞代碼如下:
synchronized(對象) {
改變條件
對象.notifyAll();
}
最後,覺得寫的不錯的同學麻煩點個贊,支持一下唄^_^~
參考與感謝
- 《Java併發編程的藝術》
- https://stackoverflow.com/questions/9866193/who-and-when-notify-the-thread-wait-when-thread-join-is-called
- https://www.jianshu.com/p/81a56497e073
- https://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html