Java 線程協(xié)作wait()、sleep()、join()

起因

近日在復(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.jpg

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;
            }
        }
    }

至此我們知道了join的內(nèi)部其實(shí)是用wait來(lái)實(shí)現(xiàn)的,所以當(dāng)我們?cè)趖2線程里調(diào)用 t1.join() 時(shí)會(huì)造成t2的阻塞,除非timeout超時(shí),或者其他線程調(diào)用了t1.notify/notifyAll才會(huì)終止這個(gè)wait,而t1線程在執(zhí)行結(jié)束的時(shí)候也會(huì)調(diào)用,這也就解釋了上面的現(xiàn)象了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容