线程之间的交流,可以使用Object超类自带的wait和notify/notifyAll 来实现。它们调用的前提是 获得锁,使用它们的代码/方法需要使用synchronize修饰。
wait()
wait()会使当前线程阻塞,然后交出锁给别的线程,自己暂停在wait()处,等重新被唤醒,获得锁之后,再往下执行。如果没有获得锁就直接使用wait()方法,会抛出一个runtimeException,具体是illegalMonitorStateException,非法监视器状态异常,监视器即是锁。wait()的使用是由try..catch..包裹的,catch的是interruptException,证明它是可以被打断的,除了正常的notify(),使用thread.interrput()也能将它唤醒立马跳到catch块中,发生异常就已经是释放锁了。
notify/notifyAll
同样的,这两方法也是需要获得锁才能操作,不然还是抛出illegalMonitorStateException。该方法主要是唤醒waiting状态的线程,让出锁,但注意不是立马就交出锁,而是执行完临界区余下的代码才交出锁的。notifyAll则是唤醒所有waitting的线程,让他们都有机会继续争锁。notify()是随机唤醒一个线程,具体哪个还是取决于操作系统多线程的管理,在经典的多生产者-多消费者案例下,notify()唤醒的有可能是自身相同类型的线程,所以这时候必须使用notifyAll()。
需要注意的是,wait()和notify()/notifyAll(),使用的必须是同一个监视器,不同的监视器是会报错的。
notify()在注释文档中写着是 随意的,但具体取决于JVM的实现。
https://www.jianshu.com/p/99f73827c616
wait/notify消息通知潜在的一些问题
notify通知过早
notify 通知的遗漏很容易理解,即 threadA 还没开始 wait 的时候,threadB 已经 notify 了,这样,threadB 通知是没有任何响应的,当 threadB 退出 synchronized 代码块后,threadA 再开始 wait,便会一直阻塞等待,直到被别的线程打断。下面用代码进行模拟:
public class EarlyNotify {
private static Object lock = new Object();
public static void main(String[] args) {
WaitThread waitThread = new WaitThread("waitThread", lock);
NotifyThread notifyThread = new NotifyThread("notifyThread", lock);
notifyThread.start();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitThread.start();
}
static class WaitThread extends Thread {
private Object lock;
public WaitThread(String name, Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " 获得锁");
System.out.println(Thread.currentThread().getName() + " 开始wait");
lock.wait();
System.out.println(Thread.currentThread().getName() + " 结束wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class NotifyThread extends Thread {
private Object lock;
public NotifyThread(String name, Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 获得锁");
System.out.println(Thread.currentThread().getName() + " 开始notify");
lock.notify();
System.out.println(Thread.currentThread().getName() + " 结束notify");
}
}
}
}
运行结果:(waitThread 一直在阻塞,程序运行不完)
notifyThread 获得锁
notifyThread 开始notify
notifyThread 结束notify
waitThread 获得锁
waitThread 开始wait
上面示例代码中启动了两个线程,一个是WaitThread,另一个是NotifyThread。NotifyThread首先启动,先调用notify方法。然后WaitThread线程才启动,调用wait方法,但是由于通知过了,wait方法就无法再获取到相应的通知,因此WaitThread会一直在wait方法出阻塞,这种现象就是通知过早的现象。
针对这种现象,解决方法,一般是添加一个状态标志,让waitThread调用wait方法前先判断状态是否已经改变了没,如果通知早已发出的话,WaitThread就不再去wait。对上面的代码进行更正:
public class EarlyNotify {
private static Object lock = new Object();
//sout已经刷新主存
private static Boolean isWaiting = true;
public static void main(String[] args) {
WaitThread waitThread = new WaitThread("waitThread", lock);
NotifyThread notifyThread = new NotifyThread("notifyThread", lock);
notifyThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitThread.start();
}
static class WaitThread extends Thread {
private Object lock;
public WaitThread(String name, Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
while (isWaiting) {
try {
System.out.println(Thread.currentThread().getName() + " 获得锁");
System.out.println(Thread.currentThread().getName() + " 开始wait");
lock.wait();
System.out.println(Thread.currentThread().getName() + " 结束wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyThread extends Thread {
private Object lock;
public NotifyThread(String name, Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 获得锁");
System.out.println(Thread.currentThread().getName() + " 开始notify");
lock.notify();
isWaiting = false;
System.out.println(Thread.currentThread().getName() + " 结束notify");
}
}
}
}
这段代码只是增加了一个 isWait 状态变量,NotifyThread调用notify方法后会对状态变量进行更新,在WaitThread中调用wait方法之前会先对状态变量进行判断,在该示例中,调用notify后将状态变量 isWait 改变为false,因此,在WaitThread中while对isWait判断后就不会执行wait方法,从而避免了Notify过早通知造成遗漏的情况。
在使用线程的等待/通知机制时,一般都要配合一个 boolean 变量值(或者其他能够判断真假的条件),在 notify 之前改变该 boolean 变量的值,让 wait 返回后能够退出 while 循环(一般都要在 wait 方法外围加一层 while 循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在 wait 方法处。这样便保证了程序的正确性。
在使用线程的等待/通知机制时,一般还都要在 while 循环中调用 wait()方法,因此需要配合使用一个 boolean 变量(或其他能判断真假的条件),满足 while 循环的条件时,进入 while 循环,执行 wait()方法,不满足 while 循环的条件时,跳出循环,执行后面的代码。
总结
在Object提供的消息通知机制应该遵循如下这些条件:
- 永远在while循环中对条件进行判断而不是if语句中进行wait条件的判断;
- 使用NotifyAll而不是使用notify。
基本的使用范式如下:
// The standard idiom for calling the wait method in Java
synchronized (sharedObject) {
while (condition) {
sharedObject.wait();
// (Releases lock, and reacquires on wakeup)
}
// do action based upon condition e.g. take or put into queue
}
网友评论