起因
近日在復(fù)習(xí)線程相關(guān)的知識,在join()方法有些遺忘,查看網(wǎng)上的各種文章也多有紕漏、錯誤,所以做一下記錄,備忘。
從一道面試題說起:wait()和sleep()的區(qū)別
這是一道特別常見的面試題,要理解它的內(nèi)在區(qū)別還需要了解一下Java的Monitor Object設(shè)計模式,這里簡單說明一下,有興趣的可以查閱相關(guān)資料。

Java Monitor.jpg
Java Monitor 從兩個方面來支持線程之間的同步,即:互斥執(zhí)行與協(xié)作。 Java 使用對象鎖 ( 使用 synchronized 獲得對象鎖 ) 保證工作在共享的數(shù)據(jù)集上的線程互斥執(zhí)行 , 使用 notify/notifyAll/wait 方法來協(xié)同不同線程之間的工作。這些方法在 Object 類上被定義,會被所有的 Java 對象自動繼承。
如上圖,線程如果獲得監(jiān)視鎖成功,將成為該監(jiān)視者對象的擁有者。在任一時刻內(nèi),監(jiān)視者對象只屬于一個活動線程 (Owner) 。擁有者線程可以調(diào)用 wait 方法自動釋放監(jiān)視鎖,進入等待狀態(tài)。
所以wait()和sleep()的區(qū)別我們可以從以下幾個方面來思考:
- wait()是Object類定義的方法,sleep()是Thread類的靜態(tài)方法;
- Thread.sleep不會導(dǎo)致鎖行為的改變,如果當前線程是擁有鎖的,那么Thread.sleep不會讓線程釋放鎖。而wait()會使當前線程讓出對象鎖,進入Wait Set(如上圖);
- Thread.sleep()和Object.wait()都會暫停當前的線程,對于CPU資源來說,不管是哪種方式暫停的線程,都表示它暫時不再需要CPU的執(zhí)行時間。OS會將執(zhí)行時間分配給其它線程。區(qū)別是,調(diào)用wait后,需要別的線程執(zhí)行notify/notifyAll才能夠重新獲得CPU執(zhí)行時間(還有一些interrupt操作)。
關(guān)于join()
我們先看一個小demo:
public class TestJoin {
public static void main(String[] args) {
System.out.println("Main start!");
long start = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
System.out.println("t1 begin! " + (System.currentTimeMillis() - start));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("t1 end! " + (System.currentTimeMillis() - start));
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2 begin! " + (System.currentTimeMillis() - start));
try {
t1.join(1000);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("t2 end! " + (System.currentTimeMillis() - start));
}
});
t1.start();
t2.start();
}
}
我們join的參數(shù)為1000
Main start!
t1 begin! 96
t2 begin! 96
t1 end! 2100
t2 end! 4098
如果我們join2000呢?
Main start!
t1 begin! 99
t2 begin! 99
t1 end! 2104
t2 end! 5105
3000?
Main start!
t1 begin! 88
t2 begin! 88
t1 end! 2091
t2 end! 5094
結(jié)論
從上面的結(jié)果來看,t1.join(timeout)讓正在運行的t2阻塞了,阻塞的時長取決于timeout的大小和t1運行的時長。
原理分析
首先我們還是先看join的源碼:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
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;
}
}
}