为什么要使用阻塞队列

之前,介绍了一下 ThreadPoolExecutor 的各参数的含义(并发编程之线程池ThreadPoolExecutor),其中有一个 BlockingQueue,它是一个阻塞队列。那么,小伙伴们有没有想过,为什么此处的线程池要用阻塞队列呢?

我们知道队列是先进先出的。当放入一个元素的时候,会放在队列的末尾,取出元素的时候,会从队头取。那么,当队列为空或者队列满的时候怎么办呢。

这时,阻塞队列,会自动帮我们处理这种情况。

当阻塞队列为空的时候,从队列中取元素的操作就会被阻塞。当阻塞队列满的时候,往队列中放入元素的操作就会被阻塞。

而后,一旦空队列有数据了,或者满队列有空余位置时,被阻塞的线程就会被自动唤醒。

这就是阻塞队列的好处,你不需要关心线程何时被阻塞,也不需要关心线程何时被唤醒,一切都由阻塞队列自动帮我们完成。我们只需要关注具体的业务逻辑就可以了。

而这种阻塞队列经常用在生产者消费者模式中。(可参看:面试官让我手写一个生产者消费者模式

常用的阻塞队列

那么,一般我们用到的阻塞队列有哪些呢。下面,通过idea的类图,列出来常用的阻塞队列,然后一个一个讲解(不懂怎么用的,可以参考这篇文章:怎么用IDEA快速查看类图关系)。

阻塞队列中,所有常用的方法都在 BlockingQueue 接口中定义。如

插入元素的方法: put,offer,add。移除元素的方法: remove,poll,take。

它们有四种不同的处理方式,第一种是在失败时抛出异常,第二种是在失败时返回特殊值,第三种是一直阻塞当前线程,最后一种是在指定时间内阻塞,否则返回特殊值。(以上特殊值,是指在插入元素时,失败返回false,在取出元素时,失败返回null)

抛异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)

**1) ArrayBlockingQueue**

这是一个由数组结构组成的有界阻塞队列。首先看下它的构造方法,有三个。

第一个可以指定队列的大小,第二个还可以指定队列是否公平,不指定的话,默认是非公平。它是使用 ReentrantLock 的公平锁和非公平锁实现的(后续讲解AQS时,会详细说明)。

简单理解就是,ReentrantLock 内部会维护一个有先后顺序的等待队列,假如有五个任务一起过来,都被阻塞了。如果是公平的,则等待队列中等待最久的任务就会先进入阻塞队列。如果是非公平的,那么这五个线程就需要抢锁,谁先抢到,谁就先进入阻塞队列。

第三个构造方法,是把一个集合的元素初始化到阻塞队列中。

另外,ArrayBlockingQueue 没有实现读写分离,也就是说,读和写是不能同时进行的。因为,它读写时用的是同一把锁,如下图所示:

2) LinkedBlockingQueue

这是一个由链表结构组成的有界阻塞队列。它的构造方法有三个。

可以看到和 ArrayBlockingQueue 的构造方法大同小异,不过是,LinkedBlockingQueue 可以不指定队列的大小,默认值是 Integer.MAX_VALUE 。

但是,最好不要这样做,建议指定一个固定大小。因为,如果生产者的速度比消费者的速度大的多的情况下,这会导致阻塞队列一直膨胀,直到系统内存被耗尽(此时,还没达到队列容量的最大值)。

此外,LinkedBlockingQueue 实现了读写分离,可以实现数据的读和写互不影响,这在高并发的场景下,对于效率的提高无疑是非常巨大的。

3) SynchronousQueue

这是一个没有缓冲的无界队列。什么意思,看一下它的 size 方法:

总是返回 0 ,因为它是一个没有容量的队列。

当执行插入元素的操作时,必须等待一个取出操作。也就是说,put元素的时候,必须等待 take 操作。

那么,有的同学就好奇了,这没有容量,还叫什么队列啊,这有什么意义呢。

我的理解是,这适用于并发任务不大,而且生产者和消费者的速度相差不多的场景下,直接把生产者和消费者对接,不用经过队列的入队出队这一系列操作。所以,效率上会高一些。

可以去查看一下 Excutors.newCachedThreadPool 方法用的就是这种队列。

这个队列有两个构造方法,用于传入是公平还是非公平,默认是非公平。

4)PriorityBlockingQueue

这是一个支持优先级排序的无界队列。有四个构造方法:

可以指定初始容量大小(注意初始容量并不代表最大容量),或者不指定,默认大小为 11。也可以传入一个比较器,把元素按一定的规则排序,不指定比较器的话,默认是自然顺序。

PriorityBlockingQueue 是基于二叉树最小堆实现的,每当取元素的时候,就会把优先级最高的元素取出来。我们测试一下:

public class Person {
private int id;
private String name; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
} public Person(int id, String name) {
this.id = id;
this.name = name;
} public Person() {
}
} public class QueueTest {
public static void main(String[] args) throws InterruptedException { PriorityBlockingQueue<Person> priorityBlockingQueue = new PriorityBlockingQueue<>(1, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getId() - o2.getId();
}
}); Person p2 = new Person(7, "李四");
Person p1 = new Person(9, "张三");
Person p3 = new Person(6, "王五");
Person p4 = new Person(2, "赵六");
priorityBlockingQueue.add(p1);
priorityBlockingQueue.add(p2);
priorityBlockingQueue.add(p3);
priorityBlockingQueue.add(p4); //由于二叉树最小堆实现,用这种方式直接打印元素,不能保证有序
System.out.println(priorityBlockingQueue);
System.out.println(priorityBlockingQueue.take());
System.out.println(priorityBlockingQueue);
System.out.println(priorityBlockingQueue.take());
System.out.println(priorityBlockingQueue); }
}

打印结果:

[Person{id=2, name='赵六'}, Person{id=6, name='王五'}, Person{id=7, name='李四'}, Person{id=9, name='张三'}]
Person{id=2, name='赵六'}
[Person{id=6, name='王五'}, Person{id=9, name='张三'}, Person{id=7, name='李四'}]
Person{id=6, name='王五'}
[Person{id=7, name='李四'}, Person{id=9, name='张三'}]

可以看到,第一次取出的是 id 最小值 2, 第二次取出的是 6 。

5)DelayQueue

这是一个带有延迟时间的无界阻塞队列。队列中的元素,只有等延时时间到了,才能取出来。此队列一般用于过期数据的删除,或任务调度。以下,模拟一下定长时间的数据删除。

首先定义数据元素,需要实现 Delayed 接口,实现 getDelay 方法用于计算剩余时间,和 CompareTo方法用于优先级排序。

public class DelayData implements Delayed {

    private int id;
private String name;
//数据到期时间
private long endTime;
private TimeUnit timeUnit = TimeUnit.MILLISECONDS; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public long getEndTime() {
return endTime;
} public void setEndTime(long endTime) {
this.endTime = endTime;
} public DelayData(int id, String name, long endTime) {
this.id = id;
this.name = name;
//需要把传入的时间endTime 加上当前系统时间,作为数据的到期时间
this.endTime = endTime + System.currentTimeMillis();
} public DelayData() {
} @Override
public long getDelay(TimeUnit unit) {
return this.endTime - System.currentTimeMillis();
} @Override
public int compareTo(Delayed o) {
return o.getDelay(this.timeUnit) - this.getDelay(this.timeUnit) < 0 ? 1: -1;
} }

模拟三条数据,分别设置不同的过期时间:

public class ProcessData {
public static void main(String[] args) throws InterruptedException {
DelayQueue<DelayData> delayQueue = new DelayQueue<>(); DelayData a = new DelayData(5, "A", 5000);
DelayData b = new DelayData(8, "B", 8000);
DelayData c = new DelayData(2, "C", 2000); delayQueue.add(a);
delayQueue.add(b);
delayQueue.add(c); System.out.println("开始计时时间:" + System.currentTimeMillis());
for (int i = 0; i < 3; i++) {
DelayData data = delayQueue.take();
System.out.println("id:"+data.getId()+",数据:"+data.getName()+"被移除,当前时间:"+System.currentTimeMillis());
}
}
}

最后结果:

开始计时时间:1583333583216
id:2,数据:C被移除,当前时间:1583333585216
id:5,数据:A被移除,当前时间:1583333588216
id:8,数据:B被移除,当前时间:1583333591216

可以看到,数据是按过期时间长短,按顺序移除的。C的时间最短 2 秒,然后过了 3 秒 A 也过期,再过 3 秒,B 过期。

常用阻塞队列 BlockingQueue 有哪些?的更多相关文章

  1. Java并发编程-阻塞队列(BlockingQueue)的实现原理

    背景:总结JUC下面的阻塞队列的实现,很方便写生产者消费者模式. 常用操作方法 常用的实现类 ArrayBlockingQueue DelayQueue LinkedBlockingQueue Pri ...

  2. Java并发(十八):阻塞队列BlockingQueue

    阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ...

  3. Java并发指南11:解读 Java 阻塞队列 BlockingQueue

    解读 Java 并发队列 BlockingQueue 转自:https://javadoop.com/post/java-concurrent-queue 最近得空,想写篇文章好好说说 java 线程 ...

  4. spring线程池ThreadPoolTaskExecutor与阻塞队列BlockingQueue

    一: ThreadPoolTaskExecutor是一个spring的线程池技术,查看代码可以看到这样一个字段: private ThreadPoolExecutor threadPoolExecut ...

  5. Java阻塞队列(BlockingQueue)实现 生产者/消费者 示例

    Java阻塞队列(BlockingQueue)实现 生产者/消费者 示例 本文由 TonySpark 翻译自 Javarevisited.转载请参见文章末尾的要求. Java.util.concurr ...

  6. 并发编程-concurrent指南-阻塞队列BlockingQueue

    阻塞队列BlockingQueue,java.util.concurrent下的BlockingQueue接口表示一个线程放入和提取实例的队列. 适用场景: BlockingQueue通常用于一个线程 ...

  7. 阻塞队列BlockingQueue用法

    多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享. 假设我们有若干生产者线程,另外又有若干个消费者线程.如果生产者线程需 ...

  8. 阻塞队列BlockingQueue用法(转)

    多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享. 假设我们有若干生产者线程,另外又有若干个消费者线程.如果生产者线程需 ...

  9. 阻塞队列 BlockingQueue

    在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利.本文 ...

随机推荐

  1. NSIS增加打包文件夹

    File /r "F:\rainwayDemo\exploer\RailwayLinesExploer\bin64\log" File  /r "models" ...

  2. CodeForces 438D The Child and Sequence (线段树 暴力)

    传送门 题目大意: 给你一个序列,要求在序列上维护三个操作: 1)区间求和 2)区间取模 3)单点修改 这里的操作二很讨厌,取模必须模到叶子节点上,否则跑出来肯定是错的.没有操作二就是线段树水题了. ...

  3. [Algo] 26. Kth Smallest Number In Sorted Matrix

    Given a matrix of size N x M. For each row the elements are sorted in ascending order, and for each ...

  4. AJAX数据传输

    AJAX = Asynchronous JavaScript and XML(异步的Javascript和XML) AJAX最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页 ...

  5. vs2015的密钥

    最近一直提示VS要登陆,登陆完就说评估期已到,搞得很烦. VS2015 enterprise版本得密钥:  HM6NR-QXX7C-DFW2Y-8B82K-WTYJV    亲测有效!!! 专业版本的 ...

  6. vue实现动态绑定class--多个按钮点击一个有一个

    <template> //v-for循环出来多个按钮,便于获取index         <span v-for="(item,index) in list" : ...

  7. bzoj1076 奖励关(概率dp)(状态压缩)

    BZOJ 1076 [SCOI2008]奖励关 Description 你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关.在这个奖励关里,系统将依次随机抛出k次宝物,每次你都可以选择吃或者不吃(必须 ...

  8. cesium初探之加载三维模型

    项目需要用到二三维地图切换,本来准备研究ArcGIS js for Web 3d,但考虑到版权的问题,决定试着用cesium来做,于是花了2天时间抱着试试看的心态把cesium从环境配置到加载三维模型 ...

  9. centos7 ModuleNotFoundError: No module named 'users'

    centos7下运行django项目时ModuleNotFoundError: No module named 'users' 由于我的项目目录是下面这样: 因为找不到users的路径 所以在mana ...

  10. WEB-文件包含漏洞详解

    title date tags layout 文件包含漏洞(File Include) 2018-10-12 post 产生原因: 由于在编写代码时避免麻烦就需要把公用的一段代码写到一个单独的文件里面 ...