簡書:capo 轉載請注明原創出處,謝謝!
前言:
今天,我們講講Object中wait和notify/notifyAll這一組方法,我們來看看JDK中關于這兩個方法的說明:
/**
引起當前線程等待直到另一個線程調用當前對象的notify方法或notify()方法或者一些其他的線程中斷當前線程,或者一個指定的時間已經過去
* 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();
我總結了一下關于這個方法使用注意事項:
- 引起當前線程等待,直到另一個線程調用此對象的notify()方法或notifyAll()方法.或者指定線程超時等待一定時間后。
- 這個超時時間單位是納秒,其計算公式為: 1000000*timeout+nanos
- 如果使用wait(0)和wait(0,0)是等價的
- 如果當前對象在沒有獲得鎖的監視器的情況下就調用wait或者notify/notifyAll方法就是拋出IllegalMonitorStateException異常
- 當前對象的wait方法會暫時釋放掉對象監視器的鎖,所以wait必須是在synchronized同步塊中使用,因為synchronized同步塊進入是默認是要獲取對象監視器的。同理notify/notifyAll操作也要在對象獲取監視器的情況下去喚醒一個等待池中的線程
- wait操作還要在一個循環中使用,防止虛假喚醒
wait/notify在工作中的應用,等待通知機制(消費者-生產者模式)
一個線程修改了一個對象的值,而另一個線程感知道了變化,然后進行相應的操作,整個過程開始于一個線程,而最終執行又是另一個線程。前者是生產者,后者是消費者。接下來我們使用wait/notify實現這個機制
package com.minglangx.object;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*
* @ClassName: WaitNotify
* @Description: 使用wait/notify實現等待通知機制
* @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對象監視器 并加鎖
synchronized (lock) {
//當條件不滿足時,繼續wait,同時只是暫時釋放了lock對象上的鎖,并將當前對象防止到對象的等待隊列中
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();
}
}
//當條件滿足時,完成工作
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() {
/*
* 獲取對象的監視器
*/
synchronized (lock) {
//獲取對象上的鎖,然后通知等待隊列中的所有對象,但這個時候不會釋放鎖
System.out.println(Thread.currentThread()
+ " 持有鎖..notify @"
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
//調用該方法后,將會把所有等待隊列中的線程全部移動到同步隊列中
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
我們看到當調用notify并沒有釋放掉對象上的鎖,而是要等待synchronized代碼塊走完在釋放掉對象上的鎖
這段代碼向我們說明了幾點
- 調用wait()方法后,線程狀態由 running 變為等待wait, 并將當前線程放入等待隊列中
- notify、notifyAll方法調用后,等待線程依舊不會從wait()返回,需要調用notify()或者notifyAll()的線程釋放掉鎖后,等待線程才有機會從wait()返回
- notify()方法是將等待隊列中一個等待線程從等待隊列移動到同步隊列中,而notifyAll則是將所有等待隊列中的線程移動到同步隊列中,被移動的線程狀態由 running變為 阻塞blocked
為此我們規范一下這個等待、通知機制(消費者,生產者模式)如何編寫
等待者(消費者)
編寫代碼步驟:
- 獲取對象上的鎖
- 如果條件不滿足,則調用對象上的wait()方法,應該使用一個while()條件判斷
- 條件滿足則執行對應的業務邏輯
其中偽代碼:
synchronized(對象) {
while(條件不滿足){
對象.wait();
}
處理對應的業務邏輯
}
通知者(生產者)
編寫代碼步驟:
1) 獲取對象上的鎖
- 改變條件
- 通知所有(一個)等待在對象上的線程
對應的偽代碼:
synchronized(對象) {
改變條件
對象.notifyAll();
}
總結:
- 使用wait或者notify()方法一定要在同步代碼塊中使用,而wait一般要在while循環中使用
- wait/notify可以實現生產者消費者模式,其原理是調用wait時將線程放入等待隊列,而調用notify時將等待隊列中的線程移動到同步隊列
- wait/notify機制是成對出現的,它們的實現依賴于鎖的同步機制