1. 程式人生 > 程式設計 >Thread類中join方法的實現原理

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這個物件鎖的執行緒,也就是主執行緒,所以主執行緒會繼續往下執行


參考:

【Java】Thread類中的join()方法原理

Java中Thread類的join方法到底是如何實現等待的?