等待/通知机制,是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程B调用了对象O的notify方法或notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作;
等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上;上述两个线程通过对象O来完成交互,而对象的wait方法和notify/notifyAll方法来完成等待方和通知方之间的交互;拥有相同锁的线程才能实现wait/notify机制;
wait方法是Object类的方法,它的作用是使当前执行wait方法的线程等待,在wait所在的代码行暂停执行,并释放锁,直到接收到通知或被中断为止;在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait方法,通过通知机制使某个线程继续执行wait方法后面的代码时,对线程的选择是按照执行wait方法的顺序确定的,并需要重新获得锁;如果调用wait方法时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此不需要try-catch语句捕获异常;
- wait方法立即释放锁
public class WaitReleaseLock {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Object lock = new Object();
MyThread t1 = new MyThread(lock);
t1.start();
MyThread t2 = new MyThread(lock);
t2.start();
}
}
class MyThread extends Thread {
private Object lock;
public MyThread(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
WaitReleaseLock waitReleaseLock = new WaitReleaseLock();
waitReleaseLock.testMethod(lock);
}
}
- sleep方法不释放锁
将上面的testMehod方法修改如下:
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait --- " + Thread.currentThread());
// lock.wait();
Thread.sleep(4000);
System.out.println("end wait --- " + Thread.currentThread());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行是同步效果,因为sleep方法不释放锁;
调用sleep方法的线程不会失去对象监视器的所有权;
notify方法要在同步方法或同步代码块中调用,即在调用前,线程必须获得锁,如果调用notify方法时没有持有适当的锁,则会抛出IllegalMonitorStateException;该方法用来通知那些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait方法的顺序对处于wait状态的线程发出一次通知,并使该线程重新获得锁;需要说明的是,执行notify方法后,当前线程不会马上释放该锁,wait状态的线程也不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,也就是退出synchronized同步区域后,当前线程才会释放锁,而wait状态的线程才可以获取该对象锁;当第一个获得该对象锁的wait状态的线程运行完毕后,它会释放该对象锁,此时如果没有再次使用notify方法,那么其他wait状态的线程因为没有得到通知,会继续处于wait状态;
- notify方法不立即释放锁
public class NotifyHoldLock {
private Object lock = new Object();
public void waitMethod() {
try {
synchronized (lock) {
System.out.println("begin wait -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
lock.wait();
System.out.println("end wait -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public void notifyMethod() {
try {
synchronized (lock) {
System.out.println("begin notify -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
lock.notify();
Thread.sleep(5000);
System.out.println("end notify -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
NotifyHoldLock notifyHoldLock = new NotifyHoldLock();
MyThreadA myThreadA = new MyThreadA(notifyHoldLock);
myThreadA.start();
Thread.sleep(100);
MyThreadB myThreadB = new MyThreadB(notifyHoldLock);
myThreadB.start();
}
static class MyThreadA extends Thread {
private NotifyHoldLock notifyHoldLock;
public MyThreadA(NotifyHoldLock notifyHoldLock) {
this.notifyHoldLock = notifyHoldLock;
}
@Override
public void run() {
notifyHoldLock.waitMethod();
}
}
static class MyThreadB extends Thread {
private NotifyHoldLock notifyHoldLock;
public MyThreadB(NotifyHoldLock notifyHoldLock) {
this.notifyHoldLock = notifyHoldLock;
}
@Override
public void run() {
notifyHoldLock.notifyMethod();
}
}
}
通过对控制台输出的时间分析,可以得出:必须执行完notify方法所在的同步代码块后才能释放锁;
- interrupt方法遇到wait方法
interrupt方法会出现InterruptException异常;
public class WaitInterruptException {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
}
}
catch (InterruptedException e) {
e.printStackTrace();
System.out.println("出异常,wait的线程被interrupt..");
}
}
static class MyThread extends Thread {
private Object lock;
public MyThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
WaitInterruptException waitInterruptException = new WaitInterruptException();
waitInterruptException.testMethod(lock);
}
}
public static void main(String[] args) {
try {
Object lock = new Object();
MyThread myThread = new MyThread(lock);
myThread.start();
Thread.sleep(5000);
myThread.interrupt();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
从上面几个示例可以整理如下:
- 执行完notify方法后,按照执行wait方法的顺序唤醒其他线程,notify方法所在的同步代码块执行完才会释放对象的锁,其他线程继续执行wait方法后面的代码;
- 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放;
- 在执行同步代码块的过程中,执行了锁所属的对象的wait方法,这个线程会释放对象锁,等待被唤醒;
等待方遵循如下原则:
1.获取对象的锁
2.如果条件不满足,那么调用对象的wait()方法,被通知后任要检查条件
3.条件满足则进行对于的逻辑
伪代码如下:
synchronized(对象) {
while (条件不满足) {
对象.wait();
}
对应的逻辑;
}
等待/通知的目的是确保等待线程从wait()方法返回时能够感知到通知线程对变量所做出的的修改;
通知方遵循如下原则:
1.获得对象的锁
2.改变条件
3.通知所有等待在对象上的线程
对应的伪代码如下:
synchronized(对象) {
改变条件
对象.notifyAll();
}
需要注意
1.使用wait(),notify()和notifyAll()时需要先调用对象加锁
2.使用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
3.notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才会有机会从wait()返回
4.notify()方法将等待队列中的一个等待线程从等待队列中移动到同步队列,而notifyAll()方法则时将等待队列中所有的线程全部移到同步队列,被移动的线程的状态由WAITING变为BLOCKED;
5.从wait()方法返回的前提是获得了所有调用对象的锁
等待超时模式
假设超时时间段时T,那么可以推断在当前时间 now + T之后就好超时;
定义如下变量:
- 等待持续时间:REMAINING = T
- 超时时间:FUTURE = now + T
这时仅需要wait(REMAINING) 即可,在wait(REMAINING)返回之后将会执行:REMAINING = FUTURE - now;如果REMAINING 小于等于0,表示已经超时,直接退出,否则将继续执行wait(REMAINING);
伪代码如下:
public synchronized Object get(long mills) throw InterruptedException {
long future = System.currentTimeMillis() + mills;
long remaining = mills;
//当超时大于0并且result返回值不满足要求
while ((result == null) && remaining > 0) {
wait(remaining);
remaining = future - System.currentTimeMillis();
}
return result;
}