5-2阻塞队列的应用-生产者消费者

1虚假唤醒

什么是虚假唤醒?

参考网上的一个问题和回答:

问题:java多线程中虚假唤醒的原因都有什么?

网上查到的定义是说,线程在没有调用过notify()和notifyAll()的情况下醒来,是虚假唤醒。这是什么原因会导致的呢?

回答:虚假唤醒(spurious wakeup)是一个表象,即在多处理器的系统下发出wait的程序有可能在没有notify唤醒的情形下苏醒继续执行。以运行在linux的hotspot虚拟机上的java程序为例,wait方法在jvm执行时实质是调用了底层pthread_cond_wait/pthread_cond_timedwait函数,挂起等待条件变量来达到线程间同步通信的效果,而底层wait函数在设计之初为了不减慢条件变量操作的效率并没有去保证每次唤醒都是由notify触发,而是把这个任务交由上层应用去实现,即使用者需要定义一个循环去判断是否条件真能满足程序继续运行的需求,当然这样的实现也可以避免因为设计缺陷导致程序异常唤醒的问题。

下面复现一下场景


class ProConShop{
    int num;
    /**
     * 店员放入商品到货架
     */
    public synchronized void incrementProduct() throws InterruptedException {
        //1判断商店是否有商品
        if (num!=0){//虚假唤醒
       //while (num!=0){
            this.wait();
        }
        //2生产商品
        num++;
        System.out.println(Thread.currentThread().getName()+"放入商品,商品号为"+num);
        //3通知消费者
        this.notifyAll();
    }

    /**
     * 顾客消费商品
     */
    public synchronized void decrementProduct() throws InterruptedException {
        //1判断商店是否有商品
        if (num==0){
            //while (num==0){
            this.wait();
        }
        //2消费商品
        System.out.println(Thread.currentThread().getName()+"号顾客购买商品,商品号为"+num);
        num--;
        //3通知生产者
        this.notifyAll();
    }
}

public class ProductTest {
    public static void main(String[] args) {
        ProConShop shop = new ProConShop();
        for (int i = 1; i <= 5; i++) {
            new Thread(()->{
                try {
                    shop.incrementProduct();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"店员").start();
        }
        for (int i = 1; i <= 5; i++) {
            new Thread(()->{
                try {
                    shop.decrementProduct();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },i+"").start();
        }
    }
}

从该地方起 num!=0 线程没有wait

该文章00多线程5.2线程通信案例:生产者消费者代码存在虚假唤醒问题

从网上找的一篇解读

解决方法:生产者消费者的方法判断语句if换成while

运行效果为:

2生产者消费者案例Lock方式

生产者消费者案例synchronized方式实现方式在 1虚假唤醒 

2.1采用lock方式代替synchronized

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

package com.nie.juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ProConShop{

    int num = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    /**
     * 店员放入商品到货架
     */
    public void incrementProduct() throws InterruptedException {
        lock.lock();
        try {
            //1判断商店是否有商品
            while (num!=0){
                this.wait();
//                condition.await();//等待
            }
            //2生产商品
            num++;
            System.out.println(Thread.currentThread().getName()+"放入商品,商品号为"+num);
            //3通知消费者
            this.notifyAll();
//            condition.signalAll();//唤醒
        }finally {
            lock.unlock();//释放锁
        }
    }

    /**
     * 顾客消费商品
     */
    public void decrementProduct() throws InterruptedException {
        lock.lock();
        try {
            //1判断商店是否有商品
            while (num==0){
                this.wait();
//                condition.await();//等待
            }
            //2消费商品
            System.out.println(Thread.currentThread().getName()+"号顾客购买商品,商品号为"+num);
            num--;
            //3通知生产者
            this.notifyAll();
//            condition.signalAll();//唤醒
        }finally {
            lock.unlock();//释放锁
        }
    }
}

public class ProductTest {
    public static void main(String[] args) {
        ProConShop shop = new ProConShop();
        for (int i = 1; i <= 5; i++) {
            new Thread(()->{
                try {
                    shop.incrementProduct();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"店员1").start();
        }
        for (int i = 1; i <= 5; i++) {
            new Thread(()->{
                try {
                    shop.decrementProduct();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"第一批顾客第"+i).start();
        }
    }
}

运行代码报错

解决方式用 Condition的等待await()和唤醒signalAll()代替之前的wait()和notifyAll()

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

2.2精准通知顺序访问

采用lock.Condition还有一个好处就是可以精准的唤醒你想执行的线程

例如:①多线程之间按顺序调用,实现A->B->C②红绿灯场景

红绿灯场景模拟代码

package com.nie.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 模拟交通灯
 */
class TrafficLight{
    Lock lock = new ReentrantLock();
    Condition green = lock.newCondition();//绿灯
    Condition yellow = lock.newCondition();//黄灯
    Condition red = lock.newCondition();//红灯
    //1-green  2-yellow 3-red
   volatile int state = 1;

    public void greenLight(){
        lock.lock();
        try {
            if (state!=1){ //判断是否为绿灯,不是的话等待
                green.await();
            } else {
                System.out.println(Thread.currentThread().getName()+"\t 绿灯行=========");
                TimeUnit.SECONDS.sleep(1);
                //通知黄灯
                state=2;
                yellow.signal();
//                green.signalAll(); //行不通
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void yellowLight(){
        lock.lock();
        try {
            if (state!=2){ //判断是否为黄灯,不是的话等待
                yellow.await();
            }else {
                System.out.println(Thread.currentThread().getName()+"\t 黄灯等一等=========");
                TimeUnit.SECONDS.sleep(1);
                //通知红灯
                state=3;
                red.signal();
//                yellow.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void redLight(){
        lock.lock();
        try {
            if (state!=3){ //判断是否为红灯,不是的话等待
                red.await();
            }else {
                System.out.println(Thread.currentThread().getName()+"\t 红灯停=========");
                TimeUnit.SECONDS.sleep(1);
                //通知绿灯
                state=1;
                green.signal();
//                red.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class TrafficLightDemo {
    public static void main(String[] args) {
        TrafficLight trafficLight = new TrafficLight();
        new Thread(()->{
            while (true) {
                trafficLight.greenLight();
            }
        }).start();
        new Thread(()->{
            while (true) {
                trafficLight.yellowLight();
            }
        }).start();
        new Thread(()->{
            while (true) {
                trafficLight.redLight();
            }
        }).start();
    }
}

打印结果为:

3阻塞队列模式生产者消费者

为什么需要BlockingQueue?

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这⼀切BlockingQueue都给你⼀⼿包办好了,使用阻塞队列后就不需要手动加锁了。

在Concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度,就像目录2生产者消费者案例Lock方式,所以我们采用阻塞队列来实现

代码示例:

package com.nie.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 阻塞队列模式 生产者消费者案例
 */
class MyResouce {
    private volatile boolean flag = true; //默认开启, 进行进行生产 + 消费
    private AtomicInteger atomicInteger = new AtomicInteger();

    BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    /**
     * 生产商品
     */
    public void prod() throws InterruptedException {
        while (flag){
            //生产商品
            String data = atomicInteger.incrementAndGet()+""; // ++i
            boolean resultValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if (resultValue){
                System.out.println(Thread.currentThread().getName()+"\t 插入队列"+ data +"...成功");
            } else {
                System.out.println(Thread.currentThread().getName()+"\t 插入队列"+ data +"...失败");
            }
            //模拟耗时操作
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace(); }
        }
        System.out.println(Thread.currentThread().getName()+"\t 老板喊停了, Flag已更新为false, 停止生产");
    }

    /**
     * 购买商品
     */
    public void cons() throws InterruptedException {
        while (flag){
            //购买商品
            String result = blockingQueue.poll(2L, TimeUnit.SECONDS);
            if (result == null || "".equals(result)) {
                //没有商品
                System.out.println(Thread.currentThread().getName() +"\t 超过2秒钟没有购买到商品, 退出消费..");
                return;
            }
            System.out.println(Thread.currentThread().getName() +"\t 取出购买商品成功...");
        }
    }

    /**
     * 老板喊停
     */
    public void stop(){
        //模拟营业时间
        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) {e.printStackTrace(); }
        System.out.println("5秒钟后, 老板喊停...");
        flag = false;
    }
}

public class ProdConsBlockQueueDemo {
    public static void main(String[] args) {
        MyResouce myResouce = new MyResouce();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动...");
            try {
                myResouce.prod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "店员A").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动...");
            try {
                myResouce.prod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "店员B").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动...");
            try {
                myResouce.cons();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "====顾客A").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动...");
            try {
                myResouce.cons();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "====顾客B").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动...");
            try {
                myResouce.cons();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "====顾客C").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动...");
            try {
                myResouce.cons();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "====顾客D").start();
        //老板喊停
        myResouce.stop();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值