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

Java Monitor 從兩個(gè)方面來(lái)支持線程之間的同步,即:互斥執(zhí)行與協(xié)作。 Java 使用對(duì)象鎖 ( 使用 synchronized 獲得對(duì)象鎖 ) 保證工作在共享的數(shù)據(jù)集上的線程互斥執(zhí)行 , 使用 notify/notifyAll/wait 方法來(lái)協(xié)同不同線程之間的工作。這些方法在 Object 類上被定義,會(huì)被所有的 Java 對(duì)象自動(dòng)繼承。
如上圖,線程如果獲得監(jiān)視鎖成功,將成為該監(jiān)視者對(duì)象的擁有者。在任一時(shí)刻內(nèi),監(jiān)視者對(duì)象只屬于一個(gè)活動(dòng)線程 (Owner) 。擁有者線程可以調(diào)用 wait 方法自動(dòng)釋放監(jiān)視鎖,進(jìn)入等待狀態(tài)。
所以wait()和sleep()的區(qū)別我們可以從以下幾個(gè)方面來(lái)思考:
- wait()是Object類定義的方法,sleep()是Thread類的靜態(tài)方法;
- Thread.sleep不會(huì)導(dǎo)致鎖行為的改變,如果當(dāng)前線程是擁有鎖的,那么Thread.sleep不會(huì)讓線程釋放鎖。而wait()會(huì)使當(dāng)前線程讓出對(duì)象鎖,進(jìn)入Wait Set(如上圖);
- Thread.sleep()和Object.wait()都會(huì)暫停當(dāng)前的線程,對(duì)于CPU資源來(lái)說(shuō),不管是哪種方式暫停的線程,都表示它暫時(shí)不再需要CPU的執(zhí)行時(shí)間。OS會(huì)將執(zhí)行時(shí)間分配給其它線程。區(qū)別是,調(diào)用wait后,需要?jiǎng)e的線程執(zhí)行notify/notifyAll才能夠重新獲得CPU執(zhí)行時(shí)間(還有一些interrupt操作)。
關(guān)于join()
我們先看一個(gè)小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é)果來(lái)看,t1.join(timeout)讓正在運(yùn)行的t2阻塞了,阻塞的時(shí)長(zhǎng)取決于timeout的大小和t1運(yùn)行的時(shí)長(zhǎng)。
原理分析
首先我們還是先看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;
}
}
}