等待唤醒机制(部分转载)

  • 等待唤醒机制基本实现
  • 等待唤醒机制阻塞队列实现0

等待唤醒机制的基本实现

一.循环等待问题

假设今天要发工资,强老板要去吃一顿好的,整个就餐流程可以分为以下几个步骤:
1.点餐
2.窗口等待出餐
3.就餐

 public static void main(String[] args) {
     // 是否还有包子
        AtomicBoolean hasBun = new AtomicBoolean();
        
        // 包子铺老板
        new Thread(() -> {
            try {
                // 一直循环查看是否还有包子
                while (true) {
                    if (hasBun.get()) {
                        System.out.println("老板:检查一下是否还剩下包子...");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("老板:没有包子了, 马上开始制作...");
                        Thread.sleep(1000);
                        System.out.println("老板:包子出锅咯....");
                        hasBun.set(true);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();


        new Thread(() -> {
            System.out.println("小强:我要买包子...");
            try {
                // 每隔一段时间询问是否完成
                while (!hasBun.get()) {
                    System.out.println("小强:包子咋还没做好呢~");
                    Thread.sleep(3000);
                }
                System.out.println("小强:终于吃上包子了....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

在这种情况下我们的老板需要不停的检查是否还有包子,而我们的小强需要不停的检查包子是否制作完成
,则暴露了一个问题:不断通过轮询机制来检测条件是否成立, 如果轮询时间过小则会浪费CPU资源,如果间隔过大,又导致不能及时获取想要的资源。

等待唤醒机制基本实现

为了解决循环等待消耗CPU以及信息及时性问题,Java中提供了等待唤醒机制。通俗来讲就是由主动变为被动, 当条件成立时,主动通知对应的线程,而不是让线程本身来询问。

我们之前的线程都是随机抢占CPU,而等待唤醒机制可以打破线程随机执行,使得线程的执行变得井然有序



  • 消费者
package com.cook.foodie;

public class Foodie extends Thread{
    @Override
    public void run(){
        /*
        1.循环
        2.通过代码块
        3.判断共享数据是否到了末尾(到了末尾)
        4.判断共享数据时候到了默认(没有到默认执行核心逻辑)
         */
        //    1.循环
        while (true){
            //2.通过代码块
            synchronized (Desk.lock){
                //3.判断共享数据是否到了末尾(到了)
                if(Desk.count == 0){
                    break;
                }else {
                    //4.执行核心逻辑
                    //先判断桌子上是否有面条
                    if(Desk.foodFlag == 0){
                        //如果没有面条继续等待
                        try {
                            Desk.lock.wait();//此时的wait方法务必用锁对象调用
                            //表示当前线程和锁进行绑定(唤醒的时候就只唤醒和这把锁绑定的线程)
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //如果有面条就吃面条
                        Desk.count--;
                        System.out.println("吃货正在吃面条,还能吃"+Desk.count+"碗面条");
                        //吃完后唤醒厨师继续做面条
                        Desk.lock.notifyAll();//唤醒和这把锁绑定的线程
                        //修改桌子的状态
                        Desk.foodFlag = 0;//将桌子的状态改为没有面条
                    }



                }
            }
        }
    }
}


  • 生产者

package com.cook.foodie;

public class Cooker extends Thread{
    @Override
    public void run(){
         /*
        1.循环
        2.通过代码块
        3.判断共享数据是否到了末尾(到了末尾)
        4.判断共享数据时候到了默认(没有到默认执行核心逻辑)
     */
        while (true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else {
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
                        //如果有就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //如果没有就制作食物
                        System.out.println("制作了一碗食物");
                        //通知(唤醒)消费者吃食物
                        Desk.lock.notifyAll();
                        Desk.foodFlag = 1;
                    }



                    //修改桌子上的食物状态
                }
            }
        }
    }


}

  • 控制者(也可以不用)
package com.cook.foodie;

public class Desk {
    //作用:控制生产者和消费者的执行
    //是否有面条 0 表示没有面条 1 表示有面条
    public static int foodFlag = 0;
    //总数量
    public static int count = 10;//一共只能吃10碗面
    //锁对象
    public static Object lock = new Object();
}

  • 测试类
package com.cook.foodie;
//测试类(唤醒机制的演示)
public class ThreadTest {
    public static void main(String[] args) {
        //创建线程对象
        Cooker c = new Cooker();
        Foodie f = new Foodie();
        //给线程设置名字
        c.setName("厨师");
        f.setName("吃货");
        //开启线程
        c.start();
        f.start();
    }
}

等待唤醒机制的阻塞队列实现

为了简便实现等待唤醒机制,Java提供了阻塞队列。通过阻塞队列的实现不同队列长度可以是固定的也可以是无限的。当放数据放不下将会阻塞,当取数据取不到也会阻塞

  • 生产者
package com.cook.foodie1;

import java.util.concurrent.ArrayBlockingQueue;

//要求对于厨师和吃货使用的都是同一个阻塞队列
public class Cooker extends Thread{
    ArrayBlockingQueue<String> queue ;
    public Cooker(ArrayBlockingQueue queue){
        this.queue= queue;
    }
    @Override
    public void run(){
      while (true){
          try {
              //不断的向队列里面放面条

              queue.put("面条");
              System.out.println("厨师放了一碗面条");
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
    }
}


  • 消费者
package com.cook.foodie1;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
   ArrayBlockingQueue<String> queue;//定义阻塞队列
   public Foodie(ArrayBlockingQueue<String> queue){
       this.queue = queue;
   }
    @Override
    public void run (){
        while (true){
            try {
               //不断从阻塞队列中获取食物
                final String food = queue.take();//take方法的底层也有锁
                System.out.println(food);

            } catch (InterruptedException e) {
               e.printStackTrace();
            }
        }
    }
}


  • 测试类
package com.cook.foodie1;

import java.util.concurrent.ArrayBlockingQueue;

//用阻塞队列实现多线程的等待唤醒机制
public class FoodieTest {
    public static void main(String[] args) {
    //细节:生产者和消费者必须使用同一个阻塞队列
        //1.创建阻塞队列并指定容量
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        //2.创建线程对象并转递阻塞队列
        Cooker c = new Cooker(queue);
        Foodie f = new Foodie(queue);
        //3.开启线程
        c.start();
        f.start();
    }
}
//没有确定可以吃多少碗,所以程序是个死循环


在我们的put和take方法中已经写了同步代码块

<转载>https://www.cnblogs.com/liqiangchn/p/12038007.html

posted @ 2023-03-07 21:58  一往而深,  阅读(30)  评论(0编辑  收藏  举报