: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.
*
* 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:
*
*
* 1000000*timeout+nanos
*
* 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)}.
*
* 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:
*
* - 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.
*
- The timeout period, specified by {@code timeout}
* milliseconds plus {@code nanos} nanoseconds arguments, has
* elapsed.
*
*
* The thread then waits until it can re-obtain ownership of the
* monitor and resumes execution.
*
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
*
* synchronized (obj) {
* while ()
* obj.wait(timeout, nanos);
* ... // Perform action appropriate to condition
* }
*
* 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 interrupted
* status 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.
*
* 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.
*
* 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:
*
* - By executing a synchronized instance method of that object.
*
- By executing the body of a {@code synchronized} statement
* that synchronizes on the object.
*
- For objects of type {@code Class,} by executing a
* synchronized static method of that class.
*
*
* 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();
}
}
}
}
}
这段代码最后输出:
我们看到当调用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机制是成对出现的,它们的实现依赖于锁的同步机制