等待通知--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

 

  

原文地址:https://www.cnblogs.com/AshOfTime/p/10705658.html