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

     

      

  • 相关阅读:
    [摘抄]数据湖,大数据的下一个变革
    linux localhost 借助nginx 支持https
    mac os 下 android studio 选择模拟器设备的时候一直显示Loading
    SpringBoot 项目遇到错误: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
    nexus-2.12.0-01 shiro漏洞升级
    IDEA 历史版本下载地址
    Postfix 554 5.7.1 Relay Access Denied
    Java SPI 机制实现项目框架高扩展性
    IDEA控制台 springboot输出中文乱码
    Setup Apache2 in Debian 9 and enable two ports for two sites
  • 原文地址:https://www.cnblogs.com/AshOfTime/p/10705658.html
Copyright © 2020-2023  润新知