Thread類中join方法的實現原理
一.簡介
join()是Thread類的一個方法,根據jdk檔案的定義,join()方法的作用,是等待這個執行緒結束,即當前執行緒等待另一個呼叫join()方法的執行緒執行結束後再往下執行。通常用於在main主執行緒內,等待其它呼叫join()方法的執行緒執行結束再繼續執行main主執行緒。
/**
* Waits for this thread to die.
*
*/
public final void join() throws InterruptedException
複製程式碼
二.使用示例
通過下面兩個例子,我們來看看使用join()方法的作用是什麼。
1.不使用join()方法的情況
public class CreateThreadTest {
public static void main(String[] args) {
System.out.println("主執行緒執行開始");
Thread threadA = new Thread(new RunnableTest(),"執行緒A");
threadA.start();
System.out.println("主執行緒執行結束");
}
}
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "執行開始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行結束" );
}
}
複製程式碼
執行結果如下:
主執行緒執行開始
執行緒A執行開始
主執行緒執行結束
執行緒A執行結束
複製程式碼
因為上述子執行緒執行時間相對較長,所以主執行緒執行結束之後子執行緒才執行結束。
2.使用了join()方法的情況
public class CreateThreadTest {
public static void main(String[] args) {
System.out.println("主執行緒執行開始");
Thread threadA = new Thread(new RunnableTest(),"執行緒A");
threadA.start();
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主執行緒執行結束");
}
}
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "執行開始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行結束");
}
}
複製程式碼
執行結果如下:
主執行緒執行開始
執行緒A執行開始
執行緒A執行結束
主執行緒執行結束
複製程式碼
對子執行緒threadA呼叫了join()方法之後,我們發現主執行緒會等待子執行緒執行結束之後才繼續往下執行。
三.join()方法的實現原理
下面通過Thread類原始碼(JDK1.8)來深入瞭解一下join()方法:
public final void join() throws InterruptedException {
join(0);
}
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(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
複製程式碼
上述程式碼,有兩個程式碼需要注意下,其一:
public final synchronized void join(long millis) throws InterruptedException {}
複製程式碼
成員方法加了synchronized說明是synchronized(this),this是誰?this就是threadA子執行緒物件本身。也就是說,主執行緒持有了threadA這個子執行緒物件的鎖。
其二:
while (isAlive()) {
wait(0);
}
複製程式碼
注意,這個wait()方法是Object類中的方法,也就是說執行wait()方法之後主線 程會釋放threadA物件的鎖,進入等待狀態,直到被再次喚醒。 大家都知道,有了wait(),必然有notify(),什麼時候才會notify呢?在jvm原始碼裡:
//一個c++函式:
void JavaThread::exit(bool destroy_vm,ExitType exit_type) ;
//裡面有一個賊不起眼的一行程式碼
ensure_join(this);
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread,thread->threadObj());
ObjectLocker lock(threadObj,thread);
thread->clear_pending_exception();
java_lang_Thread::set_thread_status(threadObj(),java_lang_Thread::TERMINATED);
java_lang_Thread::set_thread(threadObj(),NULL);
//同志們看到了沒,別的不用看,就看這一句
//thread就是當前執行緒,是啥?就是剛才例子中說的threadA執行緒
lock.notify_all(thread);
thread->clear_pending_exception();
}
複製程式碼
當子執行緒threadA執行結束的時候,jvm會自動喚醒阻塞在threadA物件上的執行緒,在我們的例子中也就是主執行緒。至此,threadA執行緒物件被notifyall了,那麼主執行緒也就能繼續跑下去了。
四.總結
在main主執行緒中呼叫threadA.join()方法,因為join() 方法是一個synchronized方法,所以主執行緒會首先持有thread執行緒物件的鎖。接下來在join()方法裡面呼叫wait()方法,主執行緒會釋放thread執行緒物件的鎖,進入等待狀態。最後,threadA執行緒執行結束,JVM會呼叫lock.notify_all(thread); 喚醒持有threadA這個物件鎖的執行緒,也就是主執行緒,所以主執行緒會繼續往下執行。
參考: