簡(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();
}
}
}
}
}
這段代碼最后輸出:
我們看到當(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)者)
編寫代碼步驟:
- 獲取對(duì)象上的鎖
- 如果條件不滿足,則調(diào)用對(duì)象上的wait()方法,應(yīng)該使用一個(gè)while()條件判斷
- 條件滿足則執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯
其中偽代碼:
synchronized(對(duì)象) {
while(條件不滿足){
對(duì)象.wait()鳖悠;
}
處理對(duì)應(yīng)的業(yè)務(wù)邏輯
}
通知者(生產(chǎn)者)
編寫代碼步驟:
1) 獲取對(duì)象上的鎖
- 改變條件
- 通知所有(一個(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ī)制