zoukankan      html  css  js  c++  java
  • 等待唤醒机制

    等待唤醒机制

    1.1 线程间通信

    概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

    比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

    为什么要处理线程间通信:

    多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

    如何保证线程间通信有效利用资源:

    多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

    1.2 等待唤醒机制

    什么是等待唤醒机制

    这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。

    就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

    wait/notify 就是线程间的一种协作机制。

    等待唤醒中的方法

    等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

    1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
    2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
    3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

    注意:

    哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

    总结如下:

    • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
    • 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

    调用wait和notify方法需要注意的细节

    1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
    2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
    3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

    1.3 生产者与消费者问题

    等待唤醒机制其实就是经典的“生产者与消费者”的问题。

    就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:

    包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

    示例代码:

    资源类:包子类
    设置包子的属性


    包子的状态:有 true 没有 false

    public class BaoZi {
        /**
         *  皮
         *  馅
         *  包子的状态:有true 没有false 初始值设为false没有包子
         */
    
        String pi;
        String xian;
        boolean flag = false;
    }
    
    

    生产者(包子铺)类:是一个线程类,可以继承Thread
    设置线程任务(run):生产包子
    对包子的状态进行判断
    true:有包子
    包子铺调用wait方法进入等待状态
    false:没有包子
    包子铺生产包子
    增加趣味性:生产两种包子
    有两种状态(i%2 == 0)
    包子铺生产好了包子
    修改包子的状态为true 有
    唤醒吃货线程,让吃货线程吃包子
    注意:
    包子铺线程和包子线程关系--->通信(互斥)
    必须同时同步技术保证两个线程只能有一个在执行
    锁对象必须保证唯一,可以使用包子对象作为锁对象
    包子铺类和吃货的类就需要把包子对象作为参数传递进来
    1.需要在成员位置创建一个包子变量
    2.使用带参数构造方法,为这个包子变量赋值

    public class BaoZiPu extends Thread{
        /**
         1.需要在成员位置创建一个包子变量
          */
        private BaoZi bz;
        // 2.使用带参数构造方法,为这个包子变量赋值
        public BaoZiPu(BaoZi bz){
            this.bz = bz;
        }
        // 设置线程任务(run):生产包子
        @Override
        public void run(){
            // 定义一个变量
            int count = 0;
            // 让包子铺一直生产包子
            while (true){
                // 必须使用同步技术保证两个线程只能有一个在执行
                synchronized (bz){
                    // 对包子的状态进行判断
                    if (bz.flag == true){
                        try {
                            bz.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 被唤醒后执行,包子铺生产包子
                    // 增加一些趣味性:交替生产两种包子
                    if (count % 2==0){
                        // 生产  薄皮三鲜馅包子
                        bz.pi = "薄皮";
                        bz.xian = "三鲜馅";
                    }else{
                        // 生产 冰皮牛肉大葱馅
                        bz.pi = "冰皮";
                        bz.xian="牛肉大葱馅";
                    }
                    count++;
                    System.out.println("包子铺正在生产:"+bz.pi+bz.xian+"包子");
                    // 生产包子需要3秒钟
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 包子铺生产好了包子
                    // 修改包子的状态为true 有
                    bz.flag = true;
                    // 唤醒吃货线程,让吃货吃包子
                    bz.notify();
                    System.out.println("包子铺已经生产好了:"+bz.pi+bz.xian+"包子");
                }
            }
        }
    }
    

    消费者(吃货)类:是一个线程类,可以继承Thread
    设置线程任务(run):吃包子
    对包子进行判断
    false:没有包子
    吃货调用wait()方法进入等待状态
    true:有包子
    吃货吃包子
    吃货吃完包子
    修改包子状态为false没有
    吃货唤醒包子铺线程,生产包子

    public class ChiHuo extends Thread{
        // 1.需要在成员变量位置创建一个包子变量
        private BaoZi bz;
        // 2.使用带参数构造方法,为这个包子变量赋值
        public ChiHuo(BaoZi bz){
            this.bz = bz;
        }
        // 设置线程任务(run):吃包子
        @Override
        public void run(){
            // 使用死循环,让吃货一致吃包子
            while (true){
                // 必须使用同步技术保证两个县城只有一个在执行
                synchronized (bz){
                    // 对包子状态进行判断
                    if (bz.flag == false){
                        try {
                            bz.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 唤醒之后执行的代码  吃包子
                    System.out.println("吃货正在吃:"+bz.pi+bz.xian+"的包子");
                    // 吃货吃完了包子 修改包子的状态为false 没有
                    bz.flag = false;
                    // 吃货唤醒包子铺线程,生产包子
                    bz.notify();
                    System.out.println("吃货已经把:"+bz.pi+bz.xian+"的包子吃完了,包子铺开始生产包子");
                    System.out.println("------------------");
                }
            }
        }
    }
    
    

    测试类:
    包含main方法,程序执行的入口,启动程序
    创建包子对象
    创建包子铺线程,开启生产包子
    创建吃货线程吃包子

    public class Demo {
        public static void main(String[] args) {
            BaoZi bz = new BaoZi();
            new BaoZiPu(bz).start();
            new ChiHuo(bz).start();
        }
    }
    
    

    部分运行结果:
    包子铺正在生产:薄皮三鲜馅包子
    包子铺已经生产好了:薄皮三鲜馅包子
    吃货正在吃:薄皮三鲜馅的包子
    吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子

    包子铺正在生产:冰皮牛肉大葱馅包子

  • 相关阅读:
    Java Native Method
    SQL语句优化
    Ibatis的环境搭建以及遇到的问题解决
    Java 构建器
    SpringMVC自定义视图 Excel视图和PDF视图
    java 枚举的常见使用方法
    mysql 根据某些字段之和排序
    MFC The Screen Flickers When The Image Zoomed
    How To Debug Qmake Pro File
    Gcc And MakeFile Level1
  • 原文地址:https://www.cnblogs.com/anke-z/p/12629475.html
Copyright © 2011-2022 走看看