等待通知--wait notify
1、简单理解
在jdk1.5之前用于实现简单的等待通知机制,是线程之间通信的一种最原始的方式。考虑这样一种等待通知的场景:A B线程通过一个共享的非volatile的变量flag来实现通信,每当A线程观察到flag为true的时候,代表着有工作需要做,A线程处理任务然后吧flag改成false。B线程负责发布任务,每次由B线程把flag改为false。
import org.junit.Test; import org.junit.runner.RunWith; public class ThreadCom_02 { private static boolean flag = false; private static final Object lock = "lock"; public static class Worker implements Runnable{ @Override public void run() { while (true){ synchronized (lock){ //获得锁之前先检查条件是否成立,如果不成立直接wait把锁释放了 while (!flag){ System.out.println("没有任务,等待....."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //成立才继续执行,执行完毕后修改共享变量,并notifyAll try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("完成任务"); flag = false; lock.notifyAll(); break; } } } } public static class Boss implements Runnable{ @Override public void run() { while (true){ synchronized (lock){ while (flag){ System.out.println("此时有任务,我就不发布任务了...."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("发布任务"); flag = true; lock.notifyAll(); } break; } } } @Test public static void main(String[] args) throws InterruptedException { Thread boss = new Thread(new Boss()); Thread worker = new Thread(new Worker()); worker.start(); Thread.sleep(10); boss.start(); } }
2、注意
- 在jdk1.5之后尽量不要去使用wait notify来实现诸如生产者消费者这种线程之间的通信模式,而是去使用JUC下的工具类。因为后者更强大,更便捷。
- wait方法永远在循环里调用。
synchronized(lock){ while(condition){ lock.wait() } //do something }
while方法判断的条件和wait方法像一个屏障一样,只有在条件满足的时候才会执行后续代码,否则就调用wait进入等待队列并释放锁。比如在消费者中线程,如果队列为空那么消费者线程不能继续执行。那为什么一定是while来判断呢?为什么if不可以呢?
当睡眠的线程被notify唤醒且获得锁后就会继续接着wait执行,如果是while那么还会再判断一次condition,如果是if就直接接着往下执行。即while会在wait返回后再次判断,if不会再次判断。那为什么需要再次判断呢?或者说在什么情况下一个调用了wait方法的线程苏醒后仍然不满足继续执行的条件?
- 线程伪唤醒。一个sleep状态的线程会莫名其妙的但是概率很低的醒过来。
- 恶意或者错误通知。当条件不成立的时候调用了notify或者条件仅仅允许一个线程苏醒的时候调用了notifyAll。
以生产者消费者模式为例,一般会使用一个共享的队列当做通信媒介。当队列中元素数目为空的时候,消费者线程如果获得了处理机资源那么也只能wait,否则消费者线程消费一个元素并notifyAll;当队列中元素满的时候,生产者线程如果获得了处理机也只能wait,否则生产者向队列里增加一个元素并notifyAll.正是notifyAll的使用导致了错误通知。
比如队列长度为1,设置了1个生产者,5个消费者。当生产者添加了一个元素使队列满了,生产者wait后释放锁。5个消费者有一个消费者抢到了锁并消费了这一个元素,然后调用notifyAll,问题就处在这个地方,调用notifyAll不仅会唤醒生产者线程,也会唤醒那四个没有获得锁的消费者线程。如果这4个消费者线程被唤醒的时候没有使用while判断condition是否成立,那么一定会出现错误的情况。
import java.util.LinkedList; import java.util.Queue; public class ThreadCon_03 { private static Queue<Integer> queue = new LinkedList<>(); public static class Consumer implements Runnable{ @Override public void run() { while (true){ synchronized (queue){ while (queue.isEmpty()){ System.out.println(Thread.currentThread().getName()+"队列为空,等待"); try { queue.wait(); System.out.println(Thread.currentThread().getName()+"被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } } queue.remove(); System.out.println(Thread.currentThread().getName()+"消费一个..."); queue.notifyAll(); } } } } public static class Producer implements Runnable{ @Override public void run() { int count = 0; while (count<5){ count++; synchronized (queue){ while (queue.size()>=1){ System.out.println("队列满了"); try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.add(1); System.out.println("添加一个"); queue.notifyAll(); System.out.println("======================="); } } } } public static void main(String[] args) throws InterruptedException { for (int i=0;i<5;i++){ Thread thread = new Thread(new Consumer()); thread.start(); } Thread.sleep(10); Thread producer = new Thread(new Producer()); producer.start(); } }
控制台打印可以看到每次抢到锁的消费者线程调用notifyAll之后会唤醒所有的消费者线程。
Thread-0队列为空,等待 Thread-1队列为空,等待 Thread-2队列为空,等待 Thread-3队列为空,等待 Thread-4队列为空,等待 添加一个 ======================= 队列满了 Thread-4被唤醒 Thread-4消费一个... Thread-4队列为空,等待 Thread-3被唤醒 Thread-3队列为空,等待 Thread-2被唤醒 Thread-2队列为空,等待 Thread-1被唤醒 Thread-1队列为空,等待 Thread-0被唤醒 Thread-0队列为空,等待 添加一个 ======================= 队列满了 Thread-0被唤醒 Thread-0消费一个... Thread-0队列为空,等待 Thread-1被唤醒 Thread-1队列为空,等待 Thread-2被唤醒 Thread-2队列为空,等待 Thread-3被唤醒 Thread-3队列为空,等待 Thread-4被唤醒 Thread-4队列为空,等待 添加一个 ======================= 队列满了 Thread-4被唤醒 Thread-4消费一个... Thread-4队列为空,等待 Thread-3被唤醒 Thread-3队列为空,等待 Thread-2被唤醒 Thread-2队列为空,等待 Thread-1被唤醒 Thread-1队列为空,等待 Thread-0被唤醒 Thread-0队列为空,等待 添加一个 ======================= 队列满了 Thread-0被唤醒 Thread-0消费一个... Thread-0队列为空,等待 Thread-1被唤醒 Thread-1队列为空,等待 Thread-2被唤醒 Thread-2队列为空,等待 Thread-3被唤醒 Thread-3队列为空,等待 Thread-4被唤醒 Thread-4队列为空,等待 添加一个 ======================= Thread-4被唤醒 Thread-4消费一个... Thread-4队列为空,等待 Thread-3被唤醒 Thread-3队列为空,等待 Thread-2被唤醒 Thread-2队列为空,等待 Thread-1被唤醒 Thread-1队列为空,等待 Thread-0被唤醒 Thread-0队列为空,等待 Process finished with exit code 130 (interrupted by signal 2: SIGINT)
所有根据上面的分析,如果我把消费者的while换成if,那么一定会越界。
总之,为了安全起见wait方法一定要写在while里,来保证wait方法返回后还需要一次判断。
3、参考
http://www.importnew.com/26584.html