浅谈阻塞队列BlockingQueue

一、什么是阻塞队列?

首先,队列我们应该都了解,是一种常见的数据结构,特点是先进先出(FIFO)

结合生活中的例子,当火锅店中人满为患座位占满时,通常的做法是安排后来的顾客在候客区等待正在就餐的顾客,而不是让顾客直接离开。此时的场景对于商家来说是希望存在阻塞(候客区)的。而在实际开发中,有时会遇到需要阻塞或者不得不阻塞的场景,为了对阻塞的线程进行管理,调度,就有了本节所讲的阻塞队列接口java.util.concurrent.BlockingQueue。这里对其下一个简单的定义:

阻塞:必须要阻塞/不得不阻塞

阻塞队列是一个队列,在数据结构中起到的作用如下图:

在这里插入图片描述
阻塞队列的特点:

  1. 当队列是空的,从队列中获取元素的操作将会被阻塞
  2. 当队列是满的,从队列中添加元素的操作将会被阻塞
  3. 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
  4. 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变的空闲起来并后续新增

二、为什么要用阻塞队列

JUC发布之前,在多线程环境下,我们不仅要控制线程的阻塞和唤醒,还要兼顾效率和线程安全,给我们的程序带来了更大的复杂度。

在多线程领域,在某些情况下会挂起(wait)线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒(notify)。在以往的多线程业务开发中,往往需要我们自己来编写代码来判断什么时候调用生产线程,什么时候调用消费线程。而BlockingQueue帮我们在“生产-消费”关系中充当了条件判断的角色,因此我们就不需要关心什么时候阻塞线程,什么时候唤醒线程,BlockingQueue帮我们一手包办啦。

三、如何使用阻塞队列

首先,让我们来看看BlockingQueue的体系架构:

在这里插入图片描述没错,你没看错,神秘的BlockingQueue原来是Queue的子接口!而Queue又是Collection接口的子接口,与我们常用的List和Set平级。BlockingQueue的实现类有很多,下面对这些类进行简单的介绍:

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列
  • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  • DelayQueue:使用优先级队列实现的延迟无界阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列(队列中永远只有一个元素,生产一个就消费一个)
  • LinkedTransferQueue:由链表组成的无界阻塞队列
  • LinkedBlockingDeque:由链表组成的双向阻塞队列

以上常用的阻塞队列有两个,即ArrayBlockingQueueLinkedBlockingQueue,本文针对ArrayBlockingQueue进行单独介绍。

了解了BlockingQueue的体系架构之后,让我们来看看BlockingQueue有哪些常用的方法。BlockingQueue的常用方法总结起来有四大类型:抛出异常、特殊值、阻塞和超时退出,这些方法的归类如下所示:

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检查element()peek()不可用不可用

下面通过代码演示更好的理解上面的归类

1.抛出异常类型

1)插入add(e)

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        //测试第一组抛出异常类型的插入add(e)方法
        testAdd1();
    }

    private static void testAdd1() {
        //初始化容量为3的阻塞队列
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //add(e)返回boolean类型:BlockingQueue public abstract boolean add(String e)
        System.out.println(blockingQueue.add("A"));
        //由于ArrayBlockingQueue底层数据结构是数组,类似于ArrayList,所以允许该队列存放相同元素
        System.out.println(blockingQueue.add("A"));
        System.out.println(blockingQueue.add("B"));
        //此时放入元素的数量超出该队列的容量
        //System.out.println(blockingQueue.add("C"));
    }
}

运行结果:
在这里插入图片描述
结论:当使用add(e)插入队列的元素数量没有超过队列的容量时,程序正常执行

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        //测试第一组抛出异常类型的插入add(e)方法
        testAdd1();
    }

    private static void testAdd1() {
        //初始化容量为3的阻塞队列
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.add("A");
        //由于ArrayBlockingQueue底层数据结构是数组,类似于ArrayList,所以允许该队列存放相同元素
        blockingQueue.add("A");
        blockingQueue.add("B");
        //此时放入元素的数量超出该队列的容量,报错
        blockingQueue.add("C");
    }
}

运行结果:
在这里插入图片描述
结论:当使用add()插入队列的元素数量超出该队列的容量时,报队列满的非法状态异常Queue full

2)移除remove()

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        //测试第一组抛出异常类型的移除remove()方法
        testRemove1();
    }

    private static void testRemove1() {
        //初始化容量为3的阻塞队列
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.add("A");
        blockingQueue.add("B");
        blockingQueue.add("C");
        //根据队列的数据结构特点,移除顺序为A-B-C
        System.out.println(blockingQueue.remove());
        //Queue public abstract String remove()
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //System.out.println(blockingQueue.remove());
    }
}

运行结果:
在这里插入图片描述
结论:当阻塞队列有元素时,使用remove()移除元素时,程序正常运行

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        //测试第一组抛出异常类型的移除remove()方法
        testRemove1();
    }

    private static void testRemove1() {
        //初始化容量为3的阻塞队列
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.add("A");
        blockingQueue.add("B");
        blockingQueue.add("C");
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
    }
}

运行结果:
在这里插入图片描述
结论:当阻塞队列为空时,使用remove()移除元素时,程序报NoSuchElementException异常

3)检查element()

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        //测试第一组抛出异常类型的检查元素element()方法
        testElement1();
    }

    private static void testElement1() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //blockingQueue.add("A");
        //blockingQueue.add("B");
        //检查阻塞队列的队首是哪个元素Queue public abstract String element()
        System.out.println(blockingQueue.element());
    }
}

运行结果:
在这里插入图片描述
结论:同remove()方法,当阻塞队列中没有元素时,调用element()方法会报NoSuchElementException异常

2.特殊值类型

1)插入offer(e)

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
    	//测试第二组特殊值类型的插入offer(e)方法
        testOffer2();
    }

    private static void testOffer2() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //BlockingQueue public abstract boolean offer(String e)
        System.out.println(blockingQueue.offer("A"));
        System.out.println(blockingQueue.offer("A"));
        System.out.println(blockingQueue.offer("B"));
        //此时放入元素的数量超出该队列的容量,返回false
        System.out.println(blockingQueue.offer("C"));
    }
}

运行结果:
在这里插入图片描述
结论:与抛出异常的add(e)方法不同,当向阻塞队列中新增元素的数量超过队列容量时,offer()方法返回false,不会报异常

2)移除poll()

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
    	//测试第二组特殊值类型的移除poll()方法
        testPoll2();
    }

    private static void testPoll2() {
        //初始化容量为3的阻塞队列
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.add("A");
        blockingQueue.add("B");
        //根据队列的数据结构特点,移除顺序为A-B-C
        //Queue public abstract String poll()
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
    }
}

运行结果:
在这里插入图片描述
结论:与抛出异常的remove()方法不同,当阻塞队列中不存在可移除的元素时,调用poll()方法返回null,不会报异常

3)检查peek()

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args) {
        //测试第二组特殊值类型的检查元素peek()方法
        testPeek2();
    }

    private static void testPeek2() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        /*blockingQueue.add("A");
        blockingQueue.add("B");*/
        //检查阻塞队列的队首是哪个元素Queue public abstract String peek()
        System.out.println(blockingQueue.peek());
    }

运行结果:
在这里插入图片描述
结论:与抛出异常的element()方法不同,当阻塞队列中不存在元素时,调用peek()方法返回null,不会报异常

3.阻塞类型

1)插入put(e)

package cn.edu.zknu.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {

    public static void main(String[] args){
    	//测试第三组阻塞类型的插入元素方法put(e)
        testPut31();
        //模拟多线程环境下解除put(e)方法造成的线程阻塞
        //testPut32();
    }

    private static void testPut31() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println(blockingQueue.add("A"));
        try{
        	//BlockingQueue public abstract void put(String e) throws InterruptedException
            //插入新的元素“C”,由于队列的容量为1,所以该队列陷入阻塞状态
            blockingQueue.put("C");
            System.out.println("程序已经被阻塞,随便敲点什么都不会被执行。。。");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    private static void testPut32(){
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println(Thread.currentThread().getName()+"-----"+blockingQueue.add("A"));
        System.out.println(Thread.currentThread().getName()+"-----初始队首元素:"+blockingQueue.element());
        //新建一个子线程
        new Thread(()->{
            //睡眠3秒
            try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
            //移除队列中的“A”元素
            System.out.println(Thread.currentThread().getName()+"-----移除队列中的元素:"+blockingQueue.remove());
        }).start();
        try{
            System.out.println(Thread.currentThread().getName()+"-----继续正常执行");
            //插入新的元素“C”,此时由于队列中的“A”还没有被移除,队列的容量为1,所以该队列陷入阻塞状态
            blockingQueue.put("C");
            System.out.println(Thread.currentThread().getName()+"-----插入新元素后队首元素:"+blockingQueue.element());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

testPut31运行结果:
在这里插入图片描述
testPut32运行结果:
在这里插入图片描述

结论:当要插入元素的阻塞队列已满时,使用put(e)方法会造成程序一直处于阻塞状态,直到从队列中移除元素才会将新元素插入该队列。

2)移除take()

package cn.edu.zknu.juc;

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

public class BlockingQueueDemo {

    public static void main(String[] args){
        //测试第三组阻塞类型的移除元素take()方法
        //testTake31();
        //模拟多线程环境下解除take()方法造成的线程阻塞
        //testTake32();
    }

    private static void testTake31() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println(blockingQueue.add("A"));
        try{
            //BlockingQueue public abstract String take() throws InterruptedException
            //移除队首的元素,返回被移除的元素
            System.out.println(blockingQueue.take());
            //由于此时阻塞队列中已经没有元素,所以队列进入阻塞状态
            System.out.println(blockingQueue.take());
            System.out.println("程序已经被阻塞,随便敲点什么都不会被执行。。。");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

	private static void testTake32() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println(Thread.currentThread().getName()+"-----初始化插入元素:"+blockingQueue.add("A"));

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"-----"+blockingQueue.remove());
            System.out.println(Thread.currentThread().getName()+"-----元素A已经被移除,队列中没有元素了。。。");
            //线程睡眠3秒
            try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) { e.printStackTrace();}
            System.out.println(Thread.currentThread().getName()+"-----新插入元素结果:"+blockingQueue.add("D"));
        },"孙培阳").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"-----进入李扬扬线程。。。");
            try {
                System.out.println(Thread.currentThread().getName()+"-----"+blockingQueue.take());
                System.out.println(Thread.currentThread().getName()+"-----不等到队列中有元素,我就不执行。。。");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"李扬扬").start();
    }
}

testTake31运行结果:
在这里插入图片描述
testTake32运行结果:
在这里插入图片描述
结论:当要移除元素的阻塞队列为空时,使用take()方法会造成程序一直处于阻塞状态,直到向队列中插入新的元素才会将该新元素从队列中移除。

4.超时退出类型

1)插入方法offer(e,time,unit)

package cn.edu.zknu.juc;

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

public class BlockingQueueDemo {

    public static void main(String[] args){
        //测试第四组超时退出类型offer(e,time,unit)方法
        testOffer41();
        //模拟多线程环境下使用offer(e,time,unit)插入新元素
        //testOffer42();
    }

    private static void testOffer41() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println(Thread.currentThread().getName()+"-----初始化插入元素:"+blockingQueue.add("A"));
        try {
            //BlockingQueue public abstract boolean offer(String e, long timeout, TimeUnit unit) throws InterruptedException
            System.out.println("超时退出插入方法结果:"+blockingQueue.offer("B", 3, TimeUnit.SECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void testOffer42() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println(Thread.currentThread().getName()+"-----初始化插入元素:"+blockingQueue.add("A"));
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"-----超时退出插入方法结果:"+blockingQueue.offer("B", 3, TimeUnit.SECONDS));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"孙培阳").start();

        new Thread(()->{
            try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) { e.printStackTrace();}
            System.out.println(Thread.currentThread().getName()+"-----移除元素结果:"+blockingQueue.remove());
            System.out.println(Thread.currentThread().getName()+"-----获取队列中最新元素:"+blockingQueue.element());
        },"李扬扬").start();
    }
}

testOffer41运行结果:
在这里插入图片描述

testOffer42运行结果:
在这里插入图片描述

结论:当要插入元素的阻塞队列已满时,使用offer(e,time,unit)方法会造成程序在指定时间内处于阻塞状态,如果在指定时间内该队列一直是满的,则结束程序。

2)移除元素poll(time,unit)

package cn.edu.zknu.juc;

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

public class BlockingQueueDemo {

    public static void main(String[] args){
        //testPoll41();
        testPoll42();
    }

    private static void testPoll41() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        System.out.println("插入新元素:"+blockingQueue.add("A"));
        System.out.println("移除元素:"+blockingQueue.remove());
        try {
            //BlockingQueue public abstract String poll(long timeout, TimeUnit unit) throws InterruptedException
            System.out.println("超时退出移除的元素:"+blockingQueue.poll(3, TimeUnit.SECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void testPoll42() {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"-----开始移除元素");
                System.out.println(Thread.currentThread().getName()+"-----超时退出移除方法结果:"+blockingQueue.poll(3, TimeUnit.SECONDS));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"孙培阳").start();

        new Thread(()->{
            try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) { e.printStackTrace();}
            System.out.println(Thread.currentThread().getName()+"-----插入元素结果:"+blockingQueue.add("P"));
        },"李扬扬").start();
    }

testPoll41运行结果:
在这里插入图片描述
testPoll42运行结果:
在这里插入图片描述
结论:当使用poll(time,unit)方法对阻塞队列进行移除元素时,如果该队列的状态为空队列时,程序会在指定时间内等到队列中有元素时再进行移除,如果超过指定时间队列中仍没有元素可以移除,则结束程序。

通过以上对ArrayBlockingQueue不同方法的探究,我们不难总结出以下结论:
在这里插入图片描述

到这里,我们对ArrayBlockingQueue有了基本的认识和了解,如果感兴趣,可以看看这些方法底层的源码,了解其通过ReentrantLock对各个方法进行线程安全以及线程通信的工作原理,对于以后的工作和学习都是有帮助的,暂时先到这里。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值