阻塞队列什么机制

什么是阻塞队列
阻塞队列:从名字可以看出,他也是队列的一种,那么他肯定是一个先进先出(FIFO)的数据结构。与普通队列不同的是,他支持两个附加操作,即阻塞添加和阻塞删除方法。

阻塞队列的特点

阻塞添加:当阻塞队列是满时,往队列里添加元素的操作将被阻塞。
阻塞移除:当阻塞队列是空时,从队列中获取元素/删除元素的操作将被阻塞。

在多线程中,阻塞的意思是,在某些情况下会挂起线程,一旦条件成熟,被阻塞的线程就会被自动唤醒。
阻塞队列不用手动控制什么时候该被阻塞,什么时候该被唤醒,简化了操作。

根据插入和取出两种类型的操作,具体分为下面一些类型:
在这里插入图片描述

抛出异常 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException

特殊值 插入方法,成功返回true 失败返回false
移除方法,成功返回元素,队列里面没有就返回null
一直阻塞 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
超时退出 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出

BlockingQueue的实现类
ArrayBlockingQueue 由数组构成的有界阻塞队列
LinkedBlockingQueue 由链表构成的有界阻塞队列
PriorityBlockingQueue 支持优先级排序的无界阻塞队列
DelayQueue 支持优先级的延迟无界阻塞队列
SynchronousQueue 单个元素的阻塞队列
LinkedTransferQueue 由链表构成的无界阻塞队列
LinkedBlockingDeque 由链表构成的双向阻塞队列

SynchronousQueue: 队列只有一个元素,如果想插入多个,必须等队列元素取出后,才能插入,只能有一个“坑位”,用一个插一个。
需要注意的是LinkedBlockingQueue虽然是有界的,但有个巨坑,其默认大小是Integer.MAX_VALUE,高达21亿,一般情况下内存早爆了(在线程池的ThreadPoolExecutor有体现)。


SynchronusQueue
他是不存储元素的,来一个,消费一个。(同一时间内只能添加一个元素)

ArrayListBlockingQueue原理
通过源码来看,其实ArrayListBlockingQueue是通过ReentrantLock和Condition条件队列来实现阻塞的。一些成员变量如下:

//存储数据
final Object[] items;

//返回获取数据的索引,主要用于take、poll、peek、remove方法
int takeIndex;

//返回添加数据的索引,主要用于 put、offer、add 方法
int putIndex;

// 队列元素的个数
int count;

//可重入锁
final ReentrantLock lock;

//条件对象,用于通知take方法队列的线程
private final Condition notEmpty;

//条件对象,用于通知put方法队列的线程
private final Condition notFull;

//迭代器
transient Itrs itrs = null;

阻塞队列的应用场景
线程池的底层存储;
生产消费队列模式。

拓展
LinkedBlockingQueue和ArrayBlockingQueue区别
队列大小不同;

arrayBlockingQueue在初始化的时候,必须指定队列的大小;
而LinkedBlockingQueue在初始化的时候,如果你没有指定大小,则会默认Integer.MAX_VALUE,是一个很大的值,这样就会导致数据在一个不可控范围,一旦添加速度远大于移除的速度时,可能会有内存泄漏的风险。

底层实现不同;
arrayBlockingQueue的底层是一个数组,而LinkedBlockingQueue底层是一个链表结构。官方文档介绍中,LinkedBlockingQueue的吞吐行是高于arrayBlockingQueue;但是在添加或移除元素中,LinkedBlockingQueue则会生成一个额外的Node对象,对GC可能存在影响;

至于为什么说LinkedBlockingQueue的吞吐性是高于arrayBlockingQueue:

吞吐性能强是因为有两个锁,试想一下,Array里面使用的是一个锁,不管put还是take行为,都可能被这个锁卡住,而Linked里面put和take是两个锁,put只会被put行为卡住,而不会被take卡住,因此吞吐性能自然强于Array。 而“less predictable performance”这个也是显而易见的,Array采用的时固定内存,而Linked采用的时动态内存,无论是分配内存还是释放内存(甚至GC)动态内存的性能自然都会比固定内存要差。

锁机制不一样;
arrayBlockingQueue使用的一个锁来控制,LinkedBlockingQueue使用了2个锁来控制,一个名为putLock,另一个是takeLock,但是锁的本质都是ReentrantLock。

传统模式
要求:初始值为0的变量,两个线程交替操作,一个+1,一个-1,执行五轮。

//资源类
class MyResource{
    int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //自增
    public void increaseNum(){
        lock.lock();
        try{
            //判断
            while(number != 0){
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            //唤醒
            condition.signalAll();
        }catch(Exception e){
            e.getStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //自减
    public void decrNum(){
        lock.lock();
        try{
            //判断
            while(number == 0){
                condition.await();
            }
            //干活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            //唤醒
            condition.signalAll();
        }catch(Exception e){
            e.getStackTrace();
        }finally {
            lock.unlock();
        }
    }

}

public class ProdConsTradTest {
    public static void main(String[] args) {
        MyResource myResource = new MyResource();
        new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                myResource.increaseNum();
            }
        },"Prod").start();

        new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                myResource.decrNum();
            }
        },"Cons").start();
    }
}

阻塞队列模式

class MyData {
    //全局开关
    private volatile boolean flag = true;

    private BlockingQueue<String> queue;

    private AtomicInteger atomicInteger =  new AtomicInteger();

    public MyData(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    public void myProd() throws InterruptedException {
        String data = null;
        boolean isOfferSuccess;
        while(flag){
            data = atomicInteger.incrementAndGet()+"";
            isOfferSuccess = queue.offer(data, 2l, TimeUnit.SECONDS);
            if(isOfferSuccess){
                System.out.println(Thread.currentThread().getName()+"线程\t 插入队列成功 \t 插入队列的值为:"+data);
            }else{
                System.out.println(Thread.currentThread().getName()+"线程\t 插入队列失败");
            }
            TimeUnit.SECONDS.sleep(1);
        }
    }
    public void myCons() throws InterruptedException {
        String result =null;
        while(flag){
            result = queue.poll(2l, TimeUnit.SECONDS);
            if(Objects.isNull(result) || result.equalsIgnoreCase("")){
                flag = false;
                System.err.println(Thread.currentThread().getName() + "\t超过2秒钟没有消费,退出消费");
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t消费队列\t消费\t" + result + "\t成功");
        }
    }
    public void stop() {
        this.flag = false;
    }
}

public class ProdConsBlockingQueueTest {
    public static void main(String[] args) {
        MyData myData = new MyData(new ArrayBlockingQueue<>(10));

        new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+" \t 生产者线程开始生产");
            try {
                myData.myProd();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Pord").start();

        new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+" \t 消费者线程开始消费");
            try {
                myData.myCons();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Cons").start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.err.println("5秒钟后,叫停");
        myData.stop();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值