阻塞队列以及阻塞队列的使用

阻塞队列:当队列中没有元素时停止拿取,当队列中元素已经满时停止向队列中添加元素。使用场景可以联想到海底捞火锅,当餐厅位置已经满时,需要一个等候区来容纳进不到餐厅的客人,需要去阻塞而不是将客人赶走。

BlockQueue核心方法

方法类型       抛出异常       特殊值       阻塞     超时

插入                add                 offer        put      offer(e,time,unit)

移除                 remove          poll         take      poll

应用实例(生产者消费者)

原始生产者消费者代码如下

import java.lang.invoke.LambdaConversionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


class ShareData{
    private int number = 0;
    private Lock lock = new ReentrantLock();//里面不传递参数为false,为非公平锁
    private Condition condition = lock.newCondition();
    //生产者
    public void inCreate() throws Exception{
        lock.lock();
        try{
            //1. 判断(多线程时不要使用if判断,可能会导致虚假唤醒)
            while (number!=0){
                //当有产品时不生产
                condition.await();
            }
            //2.干活
            number++;
            System.out.println(Thread.currentThread().getName()+"\t生产了"+number);
            //3.通知唤醒
            condition.signalAll();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
    //消费者
    public void takeCustomer() throws Exception{
        lock.lock();
        try{
            //1. 判断(多线程时不要使用if判断,可能会导致虚假唤醒)
            while (number == 0){
                //当有产品时不消费
                condition.await();
            }
            //2.干活
            number--;
            System.out.println(Thread.currentThread().getName()+"\t消费了"+number);
            //3.通知唤醒
            condition.signalAll();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

public class ProAndCusOld {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() ->{
            for(int i = 1;i<=5;i++){
                try{
                    shareData.inCreate();
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() ->{
            for(int i = 1;i<=5;i++){
                try{
                    shareData.takeCustomer();
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

可以看到使用lock时,需要手动去控制线程的唤醒,在并发环境下容易出现失误,下面是使用阻塞队列的方法

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

class MyResource{
    private volatile Boolean flag = true; //使用volatile保证可见性
    private AtomicInteger atomicInteger = new AtomicInteger();//在多线程条件下使用原子整型
    BlockingDeque<String> blockingDeque = null; //阻塞队列
    public MyResource(BlockingDeque<String> blockingDeque){ //构造注入传接口
        this.blockingDeque = blockingDeque;
        System.out.println(blockingDeque.getClass().getName());
    }
    //生产者
    public void myProd() throws Exception{
        String data = null;
        Boolean returnVal;
        while (flag){
            data = atomicInteger.incrementAndGet()+"";//原子整型加一
            returnVal = blockingDeque.offer(data,2l,TimeUnit.SECONDS);//插入队列
            if(returnVal){
                System.out.println(Thread.currentThread().getName()+"\t插入队列"+data+"成功");
            }else{
                System.out.println(Thread.currentThread().getName()+"\t插入队列"+data+"失败");
            }
            TimeUnit.SECONDS.sleep(2);
        }
        System.out.println(Thread.currentThread().getName()+"\t叫停");
    }
    //消费者
    public void myCus() throws Exception{
        String result = null;
        while (flag){
            result = blockingDeque.poll(2l,TimeUnit.SECONDS);//从队列中取出
            if(null == result || result.equalsIgnoreCase("")){
                flag = false;
                System.out.println(Thread.currentThread().getName()+"\t"+"超过两秒没有取到");
                return;
            }
            System.out.println(Thread.currentThread().getName()+"\t消费队列成功");
        }
    }
    public void stop() throws Exception{
        this.flag = false;
    }
}



public class ProAndCusNew {
    public static void main(String[] args) {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<String>(10));
        new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
            try {
                myResource.myProd();
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }).start();
        new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
            try {
                myResource.myCus();
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }).start();
        try{
            TimeUnit.SECONDS.sleep(5);
            System.out.println("5s时间到不再执行");
            myResource.stop();
        }
        catch (Exception e){
            e.printStackTrace();
        }

    }
}

可以看到在使用阻塞队列之后,不用再去控制唤醒等这些细节,可以在保证能效的前提下同时兼顾安全。

阻塞队列的思想在消息中间键和线程池等地方都有使用,原理是类似的。

发布了8 篇原创文章 · 获赞 7 · 访问量 213

猜你喜欢

转载自blog.csdn.net/weixin_39475445/article/details/104320924