zoukankan      html  css  js  c++  java
  • Java并发编程的艺术笔记(二)——wait/notify机制

    一.概述

    一个线程修改了一个对象的值,另一个线程感知到变化从而做出相应的操作。前者是生产者,后者是消费者。

    等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

    二.代码示例

    package concurrent;
    
    import java.util.concurrent.TimeUnit;
    
    public class WaitNotifyDemo2 {
        
        static final Object obj = new Object();
        private static boolean flag = false;
        
        public static void main(String[] args) throws InterruptedException {
            Thread produce = new Thread(new Produce(), "生产者线程");
            Thread consume = new Thread(new Consume(), "消费者线程");
            consume.start();
            Thread.sleep(1000);
            produce.start();
    
        }
        
        //生产者线程
        static class Produce implements Runnable {
    
            @Override
            public void run() {
                //加锁,拥有lock的monitor
                synchronized (obj) {
                    System.out.println("进入生产者线程");
                    System.out.println("生产");
                    try {
                        //模拟生产
                        TimeUnit.MILLISECONDS.sleep(2000);
                        //改变条件
                        flag = true;
                        // 获取lock的锁,然后进行通知等待在obj上的对象,通知时不会释放lock的锁,
                        // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
                        obj.notifyAll();
                        TimeUnit.MILLISECONDS.sleep(1000);  //模拟其他耗时操作
                        System.out.println("退出生产者线程");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                }
            }
            
        }
        
        //消费者线程
        static class Consume implements Runnable {
    
            @Override
            public void run() {
                // 加锁,拥有lock的Monitor
                synchronized (obj) {
                    System.out.println("进入消费者线程");
                    System.out.println("wait flag 1:" + flag);
                    // 当条件不满足时,继续wait,同时释放了lock的锁
                    while (! flag) {
                        System.out.println("还没生产,进入等待");
                        try {
                            //等待,线程被阻塞
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("结束等待");
                    }
                    
                    //满足条件,消费
                    System.out.println("wait flag 2:" + flag);
                    System.out.println("消费");
                    System.out.println("退出消费者线程");
                }
            }
            
        }
    }

    打印如下:

    进入消费者线程
    wait flag 1:false
    还没生产,进入等待
    进入生产者线程
    生产
    退出生产者线程
    结束等待
    wait flag 2:true
    消费
    退出消费者线程

    对这一过程的详解:

    在图4-3中,WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。

    三.等待/通知机制范式的提炼

    等待方遵循如下原则。

    1)获取对象的锁。

    2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

    3)条件满足则执行对应的逻辑。

    对应的伪代码如下。

    synchronized(对象) {
    while(条件不满足) {
    对象.wait();
    }
    对应的处理逻辑
    }

    通知方遵循如下原则。

    1)获得对象的锁。

    2)改变条件。

    3)通知所有等待在对象上的线程。

    对应的伪代码如下。

    synchronized(对象) {
    改变条件
    对象.notifyAll();
    }

    四.为什么要使用while循环/notifyAll

    1.为什么用while:wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行指定条件的判断,以决定是满足条件往下执行呢还是不满足条件再次wait()呢?多个线程消费,那么就极有可能出现唤醒生产者的是另一个生产者或者唤醒消费者的是另一个消费者,这样的情况下用if就必然会现类似过度生产或者过度消费的情况了

    2.使用notifyAll而不是notify

    notify适用于只有一个生产者和一个消费者的情况,如果是两个生产者两个消费者,notify()导致死锁,需使用notifyAll()

  • 相关阅读:
    JSON Web Token
    Centos 7下编译安装PHP7.2(与Nginx搭配的安装方式)
    Nginx配置详解
    Centos 7下编译安装Nginx
    PHP常用正则验证
    拼手气红包函数
    获取汉字首字母大写
    根据生日计算年龄
    ffmpeg获取视频封面图片
    对象脑图总结
  • 原文地址:https://www.cnblogs.com/lingluo2017/p/10233288.html
Copyright © 2011-2022 走看看