Java中Object對(duì)象wait/notify/notifyAll方法詳細(xì)解析

簡(jiǎn)書:capo 轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝墨微!

前言:

今天,我們講講Object中wait和notify/notifyAll這一組方法,我們來看看JDK中關(guān)于這兩個(gè)方法的說明:

/**    
       引起當(dāng)前線程等待直到另一個(gè)線程調(diào)用當(dāng)前對(duì)象的notify方法或notify()方法或者一些其他的線程中斷當(dāng)前線程,或者一個(gè)指定的時(shí)間已經(jīng)過去
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or
     * some other thread interrupts the current thread, or a certain
     * amount of real time has elapsed.
     * <p>
     
     * This method is similar to the {@code wait} method of one
     * argument, but it allows finer control over the amount of time to
     * wait for a notification before giving up. The amount of real time,
     * measured in nanoseconds, is given by:
     * <blockquote>
     * <pre>
     * 1000000*timeout+nanos</pre></blockquote>
     * <p>
     * In all other respects, this method does the same thing as the
     * method {@link #wait(long)} of one argument. In particular,
     * {@code wait(0, 0)} means the same thing as {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until either of the
     * following two conditions has occurred:
     * <ul>
     * <li>Another thread notifies threads waiting on this object's monitor
     *     to wake up either through a call to the {@code notify} method
     *     or the {@code notifyAll} method.
     * <li>The timeout period, specified by {@code timeout}
     *     milliseconds plus {@code nanos} nanoseconds arguments, has
     *     elapsed.
     * </ul>
     * <p>
     * The thread then waits until it can re-obtain ownership of the
     * monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (<condition does not hold>)
     *             obj.wait(timeout, nanos);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @param      timeout   the maximum time to wait in milliseconds.
     * @param      nanos      additional time, in nanoseconds range
     *                       0-999999.
     * @throws  IllegalArgumentException      if the value of timeout is
     *                      negative or the value of nanos is
     *                      not in the range 0-999999.
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     */
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }


 /**
     * Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation. A thread waits on an object's
     * monitor by calling one of the {@code wait} methods.
     * <p>
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread will
     * compete in the usual manner with any other threads that might be
     * actively competing to synchronize on this object; for example, the
     * awakened thread enjoys no reliable privilege or disadvantage in being
     * the next thread to lock this object.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. A thread becomes the owner of the
     * object's monitor in one of three ways:
     * <ul>
     * <li>By executing a synchronized instance method of that object.
     * <li>By executing the body of a {@code synchronized} statement
     *     that synchronizes on the object.
     * <li>For objects of type {@code Class,} by executing a
     *     synchronized static method of that class.
     * </ul>
     * <p>
     * Only one thread at a time can own an object's monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();

我總結(jié)了一下關(guān)于這個(gè)方法使用注意事項(xiàng):

  • 引起當(dāng)前線程等待,直到另一個(gè)線程調(diào)用此對(duì)象的notify()方法或notifyAll()方法.或者指定線程超時(shí)等待一定時(shí)間后。
  • 這個(gè)超時(shí)時(shí)間單位是納秒羡宙,其計(jì)算公式為: 1000000*timeout+nanos
  • 如果使用wait(0)和wait(0,0)是等價(jià)的
  • 如果當(dāng)前對(duì)象在沒有獲得鎖的監(jiān)視器的情況下就調(diào)用wait或者notify/notifyAll方法就是拋出IllegalMonitorStateException異常
  • 當(dāng)前對(duì)象的wait方法會(huì)暫時(shí)釋放掉對(duì)象監(jiān)視器的鎖,所以wait必須是在synchronized同步塊中使用,因?yàn)閟ynchronized同步塊進(jìn)入是默認(rèn)是要獲取對(duì)象監(jiān)視器的。同理notify/notifyAll操作也要在對(duì)象獲取監(jiān)視器的情況下去喚醒一個(gè)等待池中的線程
  • wait操作還要在一個(gè)循環(huán)中使用,防止虛假喚醒

wait/notify在工作中的應(yīng)用,等待通知機(jī)制(消費(fèi)者-生產(chǎn)者模式)

一個(gè)線程修改了一個(gè)對(duì)象的值,而另一個(gè)線程感知道了變化,然后進(jìn)行相應(yīng)的操作,整個(gè)過程開始于一個(gè)線程,而最終執(zhí)行又是另一個(gè)線程钞馁。前者是生產(chǎn)者,后者是消費(fèi)者匿刮。接下來我們使用wait/notify實(shí)現(xiàn)這個(gè)機(jī)制

package com.minglangx.object;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 

  * @ClassName: WaitNotify

  * @Description: 使用wait/notify實(shí)現(xiàn)等待通知機(jī)制

  * @author minglangx

  * @date 2017年9月4日 下午4:16:30

  *


  
*/
public class WaitNotify {
 
 public static boolean flag = true;
 public static Object lock = new Object();
 
 
 
 public static void main(String[] args){
     
     Thread waitTHread = new Thread(new Wait(),"WaitThread");
     waitTHread.start();
     try {
         Thread.sleep(1);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
     
     Thread notifyThread = new Thread(new Notify(),"NotifyThread");
     notifyThread.start();
     
 }
 
 
 static class Wait implements Runnable{
     
     @Override
     public void run() {
         
         //獲取 lock對(duì)象監(jiān)視器 并加鎖
         synchronized (lock) {
             //當(dāng)條件不滿足時(shí),繼續(xù)wait,同時(shí)只是暫時(shí)釋放了lock對(duì)象上的鎖训措,并將當(dāng)前對(duì)象防止到對(duì)象的等待隊(duì)列中
             while(flag) {
                 try {
                     
                     System.out.println(Thread.currentThread() 
                             + "flag is true. wait@ " 
                             + new SimpleDateFormat("HH:mm:ss")
                             .format(new Date()));
                     
                     lock.wait();
                     
                     
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 
                 
             }
             
             //當(dāng)條件滿足時(shí),完成工作
             System.out.println(Thread.currentThread() 
                     + "flag is true. wait@ " 
                     + new SimpleDateFormat("HH:mm:ss")
                     .format(new Date()));
             
             
         }
         
         
     }
     
     
     
 }
 
 static class Notify implements Runnable{
     
     @Override
     public void run() {
         /*
          * 獲取對(duì)象的監(jiān)視器
          */
         synchronized (lock) {
             //獲取對(duì)象上的鎖绩鸣,然后通知等待隊(duì)列中的所有對(duì)象,但這個(gè)時(shí)候不會(huì)釋放鎖
             System.out.println(Thread.currentThread()
                      + " 持有鎖..notify @" 
                      + new SimpleDateFormat("HH:mm:ss").format(new Date()));
             
             //調(diào)用該方法后,將會(huì)把所有等待隊(duì)列中的線程全部移動(dòng)到同步隊(duì)列中
             lock.notifyAll();
             //將條件置為 false
             flag = false;
             try {
                 Thread.sleep(5);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             
             
             
         }
         
         
         //再次加鎖
         synchronized (lock) {
             System.out.println(Thread.currentThread()
                      + " 再次持有鎖..sleep @" 
                      + new SimpleDateFormat("HH:mm:ss").format(new Date()));
             
             try {
                 Thread.sleep(5);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             
         }
         
     }
     
     
 }
 

}

這段代碼最后輸出:

image.png

我們看到當(dāng)調(diào)用notify并沒有釋放掉對(duì)象上的鎖呀闻,而是要等待synchronized代碼塊走完在釋放掉對(duì)象上的鎖

這段代碼向我們說明了幾點(diǎn)

  • 調(diào)用wait()方法后,線程狀態(tài)由 running 變?yōu)榈却齱ait, 并將當(dāng)前線程放入等待隊(duì)列
  • notify潜慎、notifyAll方法調(diào)用后,等待線程依舊不會(huì)從wait()返回,需要調(diào)用notify()或者notifyAll()的線程釋放掉鎖后,等待線程才有機(jī)會(huì)從wait()返回
  • notify()方法是將等待隊(duì)列中一個(gè)等待線程從等待隊(duì)列移動(dòng)到同步隊(duì)列中,而notifyAll則是將所有等待隊(duì)列中的線程移動(dòng)到同步隊(duì)列中垒手,被移動(dòng)的線程狀態(tài)由 running變?yōu)?阻塞blocked

為此我們規(guī)范一下這個(gè)等待倒信、通知機(jī)制(消費(fèi)者,生產(chǎn)者模式)如何編寫
等待者(消費(fèi)者)
編寫代碼步驟:

  1. 獲取對(duì)象上的鎖
  2. 如果條件不滿足,則調(diào)用對(duì)象上的wait()方法,應(yīng)該使用一個(gè)while()條件判斷
  3. 條件滿足則執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯
    其中偽代碼:
    synchronized(對(duì)象) {
    while(條件不滿足){
    對(duì)象.wait()鳖悠;
    }
    處理對(duì)應(yīng)的業(yè)務(wù)邏輯
    }

通知者(生產(chǎn)者)
編寫代碼步驟:
1) 獲取對(duì)象上的鎖

  1. 改變條件
  2. 通知所有(一個(gè))等待在對(duì)象上的線程
    對(duì)應(yīng)的偽代碼:
    synchronized(對(duì)象) {
    改變條件
    對(duì)象.notifyAll();
    }

總結(jié):

  • 使用wait或者notify()方法一定要在同步代碼塊中使用,而wait一般要在while循環(huán)中使用
  • wait/notify可以實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式,其原理是調(diào)用wait時(shí)將線程放入等待隊(duì)列,而調(diào)用notify時(shí)將等待隊(duì)列中的線程移動(dòng)到同步隊(duì)列
  • wait/notify機(jī)制是成對(duì)出現(xiàn)的,它們的實(shí)現(xiàn)依賴于鎖的同步機(jī)制
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竞穷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鳞溉,更是在濱河造成了極大的恐慌,老刑警劉巖熟菲,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朴恳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡允蚣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門森渐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冒晰,“玉大人,你說我怎么就攤上這事壶运。” “怎么了蒋情?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵棵癣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我浙巫,道長(zhǎng),這世上最難降的妖魔是什么的畴? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任丧裁,我火速辦了婚禮,結(jié)果婚禮上煎娇,老公的妹妹穿的比我還像新娘。我一直安慰自己缓呛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布因妙。 她就那樣靜靜地躺著,像睡著了一般铣耘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜗细,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音蚪战,去河邊找鬼橱野。 笑死善玫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜗元。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼奕扣,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼掌敬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奔害,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芯杀,沒想到半個(gè)月后雅潭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筛圆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年椿浓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漾岳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粉寞。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唧垦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出振亮,到底是詐尸還是另有隱情,我是刑警寧澤坊秸,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布澎怒,位于F島的核電站,受9級(jí)特大地震影響喷面,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惧辈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望念逞。 院中可真熱鬧边翁,春花似錦、人聲如沸倒彰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至创淡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間誊酌,已是汗流浹背部凑。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工涂邀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人比勉。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓驹止,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親臊恋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容