最近看多線程的時候發現對於join的理解有些錯誤,在網上查了很多資料,根據本身的理解整理了一下,這裏之因此把join和wait放在一塊兒,是由於join的底層實現就是基於wait的,一併講解更容易理解。多線程
瞭解join就先須要瞭解wait,wait是線程間通訊經常使用的信號量,做用就是讓線程暫時中止運行,等待其餘線程使用notify來喚醒或者達到必定條件本身甦醒。
wait是一個本地方法,屬於Object類,其底層實現是JVM內部實現,是基於monitor對象監視鎖。ide
//本地方法 public final native void wait(long timeout) throws InterruptedException; //參數有納秒和毫秒 public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } //存在納秒,默認加一毫秒 if (nanos > 0) { timeout++; } wait(timeout); } //無參數,默認爲0 public final void wait() throws InterruptedException { wait(0); }
根據源碼能夠發現,雖然wait有三個重載的方法,可是主要的仍是wait(long timeout)這個本地方法,其餘兩個都是基於這個來封裝的,由JVM底層源碼不太好看到,我就以流程的形式來描述。this
synchronized (this) { System.out.println("A begin " + System.currentTimeMillis()); try { wait(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A end " + System.currentTimeMillis()); }
而對於時間的參數timeout須要注意的是,若是輸入0不表明不暫停,而是須要特殊狀況本身甦醒或者notify喚醒,這裏有個特殊點,wait(0)是能夠本身甦醒的。線程
public class Thread2 extends Thread{ private Thread1 a; public Thread2(Thread1 a) { this.a = a; } @Override public void run() { synchronized (a) { System.out.println("B begin " + System.currentTimeMillis()); try { a.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B end " + System.currentTimeMillis()); } } } public class Thread1 extends Thread{ @Override public void run() { synchronized (this) { System.out.println("A begin " + System.currentTimeMillis()); System.out.println("A end " + System.currentTimeMillis()); } } } public class Main{ public static void main(String[] args) { Thread1 thread = new Thread1(); Thread2 thread2 = new Thread2(thread); thread2.start(); thread.start(); System.out.println("main end "+System.currentTimeMillis()); } }
這個例子運行結果存在如下狀況code
B begin 1494995803564 main end 1494995803565 A begin 1494995803565 A end 1494995803565 B end 1494995803565
wait()在沒有notify()狀況下自動甦醒了,所以這裏能夠看到,當前狀況下Thread.wait()等待過程當中,若是Thread結束了,是能夠自動喚醒的。這個會在join中被使用。對象
瞭解了wait的實現原理以後就能夠來看join了,join是Thread類的方法,不是底層本地方法,這裏能夠看一下它的源碼。同步
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; //參數判斷<0,拋異常 if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } //參數爲0,即join() if (millis == 0) { //當前線程存活,就調用wait(0),一直到調用join的線程結束再自動甦醒 while (isAlive()) { wait(0); } //參數>0,調用wait(long millis)等待一段時間後自動喚醒 } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); } public final void join() throws InterruptedException { join(0); }
很明顯,join的三個重載方法主要仍是基於join(long millis)方法,所以咱們主要關注這個方法,方法的處理邏輯以下源碼
因爲join是synchronized修飾的同步方法,所以會出現join(long millis)阻塞時間超過了millis的值。it
public class Thread1 extends Thread{ @Override public void run() { synchronized (this) { System.out.println("A begin " + System.currentTimeMillis()); try { sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A end " + System.currentTimeMillis()); } } } public class Main{ public static void main(String[] args) { Thread1 thread = new Thread1(); thread.start(); try { thread.join(1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("main end "+System.currentTimeMillis()); } }
這個例子的運行結果是io
A begin 1494996862054 A end 1494996867056 main end 1494996867056
main線程必定是最後執行完的,按照流程來講,1秒以後阻塞就結束了,main線程應該就能夠開始執行了,可是這裏有一個注意點,join(long millis)在執行millis>0的時候在wait(delay)以後還有一行代碼,而上面代碼1秒以後只是結束了wait方法,並無執行完join方法。上面的例子,因爲join的鎖和thread的鎖相同,在thread運行完以前,鎖不會釋放,那麼致使join一直阻塞在最後一步沒法結束,纔會出現上面的狀況。