若是一個線程A執行了thread.join()語句,其含義是:當前線程A等待thread線程終止以後才從thread.join()返回。html
線程Thread除了提供join()方法以外,還提供了join(long millis)和join(long millis,int nanos)兩個具有超時特性的方法。這兩個超時方法表示,若是線程thread在給定的超時時間裏沒有終止,那麼將會從該超時方法中返回。java
首先介紹下線程的狀態linux
Java線程在運行的生命週期中可能處於6種不一樣的狀態,在給定的一個時刻,線程只能處於其中的一個狀態。以下內容截取JDK 1.8 Thread.java的源碼:c++
再介紹下Monitor編程
Monitor是 Java中用以實現線程之間的互斥與協做的主要手段,它能夠當作是對象的鎖。每個對象都有,也僅有一個 monitor。bash
在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個參數:併發
當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合,當線程獲取到對象的monitor 後進入 _Owner 區域並把monitor中的owner變量設置爲當前線程。同時monitor中的計數器count加1,若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復爲null,count自減1,同時該線程進入 _WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其餘線程進入獲取monitor(鎖)。以下圖所示:ide
一個簡單的例子。ui
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);
...
}
複製代碼
public final void join() throws InterruptedException {
join(0);
}
...
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
...
}
複製代碼
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; 複製代碼
/**
* 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()方法,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全部等待在當前線程實例上的其餘線程。
除了看C++的源碼驗證,咱們也寫了一個demo來驗證這點,waitThread執行完結束後後,wait()在waitThread對象實例的其餘線程纔會被喚醒繼續執行。
/**
* Wait Thread wait thread.
* Run Thread1 run thread outer.
* Run Thread2 run thread outer.
* Run Thread1before wait run thread inner.
* Run Thread2before wait run thread inner.
* exit: Wait Thread wait thread.
* Run Thread2after wait run thread inner.
* Run Thread1after wait run thread inner.
*/
public static void main(String[] args) throws Exception {
WaitThread waitRunner = new WaitThread();
Thread waitThread = new Thread(waitRunner, "Wait Thread");
waitThread.start();
RunThread runRunner1 = new RunThread(waitThread);
RunThread runRunner2 = new RunThread(waitThread);
Thread runThread1 = new Thread(runRunner1, "Run Thread1");
Thread runThread2 = new Thread(runRunner2, "Run Thread2");
runThread1.start();
runThread2.start();
}
static class WaitThread implements Runnable {
@Override
public void run() {
long t1 = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " wait thread.");
while (true) {
long t2 = System.currentTimeMillis();
if (t2 - t1 > 10 * 1000) {
break;
}
}
System.out.println("exit: " + Thread.currentThread().getName() + " wait thread.");
}
}
static class RunThread implements Runnable {
private final Thread thread;
public RunThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run thread outer.");
synchronized (thread) {
System.out.println(Thread.currentThread().getName() + "before wait run thread inner.");
try {
thread.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "after wait run thread inner.");
}
}
}
}
複製代碼
那麼,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逐點分析:
threadA的狀態爲WAIT
能夠發現Thread.join()方法與等待/通知的經典範式中的等待範式一模一樣。 而Thread.exit()方法則有點相似於其中的通知範式。
等待/通知的經典範式分爲兩個部分:等待方和通知方。 等待方遵循以下原則:
synchronized(對象) {
while(條件不知足) {
對象.wait();
}
對應的處理邏輯
}
複製代碼
通知方遵循以下原則:
synchronized(對象) {
改變條件
對象.notifyAll();
}
複製代碼
最後,以爲寫的不錯的同窗麻煩點個贊,支持一下唄^_^~