wait(), notify(), notifyAll(),join(),sleep(),yield()等方法介紹

一.概述

本篇文章會對線程等待/喚醒方法進行介紹。涉及到的內容包括:
1. wait(), notify(), notifyAll(),join(),sleep(),yield()等方法介紹

2. wait()和notify()

3. wait(long timeout)和notify()

4. wait() 和 notifyAll()

5.wait()和join() 

6. 爲什麼notify(), wait()等函數定義在Object中,而不是Thread中?

7.wait()和sleep(),yield()的區別

二.詳細分析

1. wait(), notify(), notifyAll(),join(),sleep(),yield()等方法介紹

在Object.java中,定義了wait(), notify()和notifyAll()等方法。

wait()的作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。

notify()喚醒在此對象鎖上等待的單個線程。
notifyAll()喚醒在此對象鎖上等待的所有線程。
wait()讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout) 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。

在Thread.java中,定義了join(),sleep(),yield()等方法。

join方法把指定的線程添加到當前線程中,可以不給參數直接thread.join(),也可以給一個時間參數,單位爲毫秒thread.join(100)。事實上join方法是通過wait方法來實現的。比如線程A中加入了線程B.join方法,則線程A默認執行wait()方法,釋放資源進入等待狀態,此時線程B獲得資源,執行結束後釋放資源,線程A重新獲取自CPU,繼續執行,由此實現線程的順序執行。

sleep()方法導致了程序暫停執行指定的時間,讓出cpu給其他線程,但是它的監控狀態依然保持者,當指定的時間到了又會自動甦醒,並返回到可運行狀態,不是運行狀態。sleep()中指定的時間是線程不會運行的最短時間。因此,sleep()方法不能保證該線程睡眠到期後就開始執行。在調用sleep()方法的過程中,線程不會釋放對象鎖。

yield意味着放手,放棄,投降。一個調用yield()方法的線程告訴虛擬機它樂意讓其他線程佔用自己的位置。這表明該線程沒有在做一些緊急的事情。注意,這僅是一個暗示,並不能保證不會產生任何影響。

讓我們列舉一下關於以上定義重要的幾點:

  • Yield是一個靜態的原生(native)方法
  • Yield告訴當前正在執行的線程把運行機會交給線程池中擁有相同優先級的線程
  • Yield不能保證使得當前正在運行的線程迅速轉換到可運行的狀態
  • 它僅能使一個線程從運行狀態轉到可運行狀態,而不是等待或阻塞狀態

2. wait()和notify()示例

/**
 * @author zhaoj
 * @version MyThread.java, v 0.1 2019-04-18 10:45
 */
public class MyThread extends Thread {
    private Object object;

    public MyThread(String name, Object object) {
        super(name);
        this.object = object;
    }

    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                object.wait();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }
}

/**
 * @author zhaoj
 * @version ThreadTest.java, v 0.1 2019-04-18 10:44
 */
public class ThreadTest {
    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1", object);
        t1.start();
        //休眠1秒讓線程t1先進入等待狀態,防止主線程先執行
        Thread.sleep(1000);
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " call notify()");
                // 主線程通過notify()喚醒線程t1。
                object.notify();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果:

t1 wait
main call notify()
main continue
t1 continue

運行結果說明:

(1)主線程main通過new MyThread("t1", object)創建t1線程,然後再通過t1.start()開啓t1線程(進入就緒狀態,即可運行狀態),隨後主線程進入休眠1秒(此時不休眠有可能主線程先獲得到對象鎖object,t1線程就一直處於等待狀態了,爲了演示效果,讓主線程進入休眠,確保t1線程可以先獲得對象鎖),進入阻塞狀態;

(2)t1線程獲得到對象鎖object(進入運行狀態),執行了run方法中的wait()方法,進入等待(阻塞狀態),同時釋放了對象鎖object,等待主線程的喚醒;

(3)主線程休眠結束(進入可運行狀態),獲得到對象鎖object(運行狀態),執行喚醒notify(),此時t1線程進入就緒狀態(即可運行狀態),此時主線程還擁有對象鎖,繼續執行下面的操作,直至所有操作執行結束,釋放對象鎖object,此時主線程等待t1線程執行;

(4)t1線程又獲得對象鎖object(運行狀態),繼續執行後面的操作,直至所有操作執行結束,然後釋放對象鎖object,此時t1線程執行完畢(即死亡狀態),主線程同時也結束等待,執行完畢(死亡狀態)。

3. wait(long timeout)和notify()

wait(long timeout)會讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”,即可運行狀態)。

下面的示例就是演示wait(long timeout)在等待超時情況下,線程被喚醒的情況:

/**
 * @author zhaoj
 * @version MyThread.java, v 0.1 2019-04-18 10:45
 */
public class MyThread extends Thread {
    private Object object;

    public MyThread(String name, Object object) {
        super(name);
        this.object = object;
    }

    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                object.wait(5000);
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}



/**
 * @author zhaoj
 * @version ThreadTest.java, v 0.1 2019-04-18 10:44
 */
public class ThreadTest {
    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1", object);
        t1.start();
        //休眠1秒讓線程t1先進入等待狀態,防止主線程先執行
        Thread.sleep(1000);
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " I come in");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

運行結果:

t1 wait
main I come in
t1 continue

運行結果說明:

(1)主線程main通過new Thread("t1",object)創建線程t1,然後主線程main執行了t1.start(),t1線程進入了"就緒狀態(可運行狀態)",此時主線程進入了休眠(阻塞狀態),爲了確保t1線程先能夠獲得對象鎖,才進行休眠的;

(2)t1線程獲得對象鎖oject(運行狀態),執行object.wait(5000),等待5秒,進入"阻塞狀態",釋放對象鎖;

(3)主線程main休眠結束(就緒狀態,即可運行狀態),主線程main獲得對象鎖object(運行狀態),執行後續的操作完成,釋放對象鎖,此時t1線程還處於等待狀態(阻塞狀態),主線程此時等待t1線程執行完成主;

(4)5秒後,object.wait(5000)等待時間超時,t1線程重新進入“就緒狀態”(即可運行狀態),由於主線程執行操作結束,t1重新獲得對象鎖oject(運行狀態),執行後續操作完成,釋放對象鎖object,執行結束(死亡狀態),主線程等待t1線程執行結束,自己也結束等待狀態(進入死亡狀態)。

4. wait() 和 notifyAll()

上面的示例是notify()喚醒單個線程在對象鎖上的等待,下面的示例是notifyAll()喚醒所有線程在對象鎖上的等待:

/**
 * @author zhaoj
 * @version MyThread.java, v 0.1 2019-04-18 10:45
 */
public class MyThread extends Thread {
    private Object object;

    public MyThread(String name, Object object) {
        super(name);
        this.object = object;
    }

    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                object.wait();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 * @author zhaoj
 * @version ThreadTest.java, v 0.1 2019-04-18 10:44
 */
public class ThreadTest {
    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1", object);
        MyThread t2 = new MyThread("t2", object);
        MyThread t3 = new MyThread("t3", object);
        t1.start();
        t2.start();
        t3.start();
        //休眠1秒讓線程t1先進入等待狀態,防止主線程先執行
        Thread.sleep(1000);
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " start");
                //主線程通過notifyAll()喚醒所有該對象鎖上的等待線程
                object.notifyAll();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

運行結果:

t1 wait
t2 wait
t3 wait
main start
main continue
t3 continue
t2 continue
t1 continue

運行結果說明:

(1)主線程main通過new Thread()創建了線程t1,t2,t3,然後執行t1.start(),t2.start(),t3.start()開啓三個線程,t1,t2,t3分別進入“就緒狀態”,即可運行狀態,主線程main進入休眠(阻塞狀態),t1,t2,t3分別爭搶對象鎖oject的擁有權,誰先獲得對象鎖object,誰先執行操作(object.wait()),進入等待狀態(阻塞狀態),根據結果可以看出t1先獲得,其次是t2,最後是t3;

(2)主線程休眠結束(可運行狀態),獲得對象鎖object(運行狀態),執行object.notifyAll()喚醒所有該對象鎖object上的等待線程t1,t2,t3,此時主線程main還未執行完所有操作,繼續執行,執行完所有動作後,釋放對象鎖object,等待t1,t2,t3執行;

(3)t1,t2,t3再次爭搶對象鎖object,先獲得鎖的先執行,根據執行結果可以看出t3先獲得,其次t2,最後t1,當所有線程執行結束,均進入死亡狀態。

5.wait()和join() 

先看一下join()的源碼:

    /**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @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 void join() throws InterruptedException {
        join(0);
    }


    /**
     * 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方法實際上是通過調用wait()方法, 來實現同步的效果的。例如,A線程中調用了B線程的join()方法,則相當於A線程調用了B線程的wait()方法,在調用了B線程的wait()方法後,A線程就會進入阻塞狀態,因爲它相當於放棄了CPU的使用權。需要注意的是,jdk規定,join(0)的意思不是A線程等待B線程0秒,而是A線程等待B線程無限時間,直到B線程執行完畢,即join(0)等價於join()。

下面看個小例子:

/**
 * @author zhaoj
 * @version ThreadNum.java, v 0.1 2019-04-18 11:34
 */
public class ThreadNum extends Thread {
    public ThreadNum(String name) {
        super(name);
    }

    @Override
    public void run() {
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + " start");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + " end");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 * @author zhaoj
 * @version ThreadNumTest.java, v 0.1 2019-04-18 11:37
 */
public class ThreadNumTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadNum t1 = new ThreadNum("t1");
        ThreadNum t2 = new ThreadNum("t2");
        ThreadNum t3 = new ThreadNum("t3");
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        t3.start();
        t3.join();
        System.out.println(Thread.currentThread().getName()+" continue");
    }
}

執行結果:

t1 start
t1 end
t2 start
t2 end
t3 start
t3 end
main continue

運行結果分析:

(1)主線程main通過new Thread()創建了線程t1,t2,t3;

(2)然後執行t1.start()開啓線程t1(t1進入就緒狀態,即可運行狀態),然後執行t1.join()方法,此時相當於主線程main調用了t1線程的wait()方法,主線程main進入等待狀態(阻塞狀態),直到線程t1執行結束(死亡狀態),才繼續往下執行

(3)t2,t3同理,這樣就可以實現三個線程按順序執行。

(4)可能有人會有疑問,主線程main執行了t1.join(),而join()的實現是wait()方法,哪爲什麼不是t1線程進入等待狀態(阻塞狀態)呢?這裏請注意理解wait()方法的作用,上面有提到,wait()方法讓當前線程處於“等待(阻塞)狀態”,當前線程正是主線程main,所以其實是主線程main調用了t1線程的wait()方法,而不是t1線程內部調用了wait()方法,兩者是有很大的區別的(打個不恰當的例子,a有一輛車,b有一輛車,a不開自己的車,開b的車,對b本身並無影響)。有人或許有還有疑問,那既然相當於主線程main調用了t1線程的wait()方法,那沒人其他線程喚醒主線程main,主線程main不應該一直等待嗎?對於這個問題,請看清join()的源碼,while (isAlive())  { wait(0);}這段代碼的意思是當線程t1還在執行(即線程存活),則繼續等待,否則繼續往下執行,也就是說主線程main調用了t1線程的wait()方法,t1線程只要存活着,主線程就一直等待,t1線程執行結束(即死亡狀態),隨之等待也就結束。

另外join(long millis)是通過在內部使用wait(long millis)方法來實現的,所有它其實是具有釋放鎖的特點的,在執行完;而sleep(long millis)是不釋放鎖的,也就是如果有synchronized同步代碼塊,其他線程仍然獲得不到鎖,無法訪問共享數據。

6. 爲什麼notify(), wait()等函數定義在Object中,而不是Thread中?

Object中的wait(), notify()等方法,和synchronized一樣,會對“對象的同步鎖”進行操作。(關於鎖的內容,本篇暫不深入,後期會單獨出一篇鎖相關的內容)

wait()會使"當前線程"等待,因爲線程進入等待狀態,所以線程應該釋放它所持有的"同步鎖",否則其它線程獲取不到該"同步鎖"而無法運行!
OK,線程調用wait()方法之後,會釋放它所持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒。現在,請思考一個問題:notify()是依據什麼喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什麼關聯起來的?答案是:依據“對象的同步鎖”。

負責喚醒等待線程的那個線程(我們稱爲“喚醒線程”),它只有在獲取“該對象的同步鎖”(這裏的同步鎖必須和等待線程的同步鎖是同一個),並且調用notify()或notifyAll()方法之後,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因爲喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之後,等待線程才能獲取到“對象的同步鎖”進而繼續運行。

總之,notify(), wait()依賴於“同步鎖”,而“同步鎖”是對象鎖持有,並且每個對象有且僅有一個!這就是爲什麼notify(), wait()等函數定義在Object類,而不是Thread類中的原因。

7.wait()和sleep(),yield()的區別

類型 來源 是否釋放鎖 作用
wait() Oject 使當前線程等待
sleep() Thread 使當前線程休眠
yield() Thread 使當前線程讓出CPU使用權

 

通過上面的表格我們可以看出:wait()方法是Oject對象所有,使當前線程進入等待狀態,並釋放所有用的對象鎖;sleep()是Thread類所有,使當前線程休眠,如果擁有鎖,將繼續持有該鎖,不會釋放;yield()也是Thread類所有,使當前線程讓出CPU使用權,但是並不能保證其他線程就一定會執行,由於讓出使用權並不是等待或者阻塞狀態,如果該線程擁有對象鎖,其他該對象鎖上等待線程也不會擁有該鎖,直到該線程執行結束纔會釋放鎖。

以上如有不正確之處,歡迎留言批評指正。

參考文獻:

http://www.cnblogs.com/skywang12345/p/3479224.html

https://blog.csdn.net/qq_38162448/article/details/81676235

http://www.importnew.com/14958.html

https://blog.csdn.net/striveb/article/details/83585380

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章