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

起因

近日在復(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;
            }
        }
    }

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

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

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

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,602評論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,115評論 1 18
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,472評論 3 87
  • 我一直覺得自己是活在記憶中的人,不是活在記憶里哀嘆今天的種種,而是讓記憶中的自己,無時無刻的鞭撻自己!我怕時光消耗...
    雪落重陽閱讀 494評論 11 12
  • 已經(jīng)好長時間沒有關(guān)注群里的信息了,也不爬樓了,就好像自己脫離了這個圈子,徹底回到了沉睡的靈魂中,其實是在逃避,逃避...
    郭騰達閱讀 833評論 0 0

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