介绍

阻塞队列(BlockingQueue)是指当队列满时,队列会阻塞插入元素的线程,直到队列不满;当队列空时,队列会阻塞获得元素的线程,直到队列变非空。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

当线程 插入/获取 动作由于队列 满/空 阻塞后,队列也提供了一些机制去处理,或抛出异常,或返回特殊值,或者线程一直等待...

方法/处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e, timeout, unit)
移除方法 remove(o) poll() take() poll(timeout, unit)
检查方法 element() peek() — 不移除元素 不可用 不可用

tips: 如果是无界阻塞队列,则 put 方法永远不会被阻塞;offer 方法始终返回 true。

Java 中的阻塞队列:

ArrayBlockingQueue

ArrayBlockingQueue 是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序,默认情况下不保证线程公平的访问。

通过可重入的独占锁 ReentrantLock 来控制并发,Condition 来实现阻塞。

public class ArrayBlockingQueueTest {

    /**
* 1. 由于是有界阻塞队列,需要设置初始大小
* 2. 默认不保证阻塞线程的公平访问,可设置公平性
*/
private static ArrayBlockingQueue<String> QUEUE = new ArrayBlockingQueue<>(2, true); public static void main(String[] args) throws InterruptedException { Thread put = new Thread(() -> {
// 3. 尝试插入元素
try {
QUEUE.put("java");
QUEUE.put("javaScript");
// 4. 元素已满,会阻塞线程
QUEUE.put("c++");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
put.start();
Thread take = new Thread(() -> {
try {
// 5. 获取一个元素
System.out.println(QUEUE.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
take.start();
// 6 javaScript、c++
System.out.println(QUEUE.take());
System.out.println(QUEUE.take());
}
}

LinkedBlockingQueue

LinkedBlockingQueue 是一个用单向链表实现的有界阻塞队列。此队列的默认最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序,吞吐量通常要高于ArrayBlockingQueue。Executors.newFixedThreadPool() 就使用了这个队列。

和 ArrayBlockingQueue 一样,采用 ReentrantLock 来控制并发,不同的是它使用了两个独占锁来控制消费和生产,通过 takeLock 和 putLock 两个锁来控制生产和消费,互不干扰,只要队列未满,生产线程可以一直生产;只要队列不空,消费线程可以一直消费,不会相互因为独占锁而阻塞。

tips:因为使用了双锁,避免并发计算不准确,使用了一个 AtomicInteger 变量统计元素总量。

LinkedBlockingDeque

LinkedBlockingDeque 是一个由双向链表结构组成的有界阻塞队列,可以从队列的两端插入和移出元素。它实现了BlockingDeque接口,多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以 First 单词结尾的方法,表示插入、获取或移除双端队列的第一个元素。以 Last 单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。

LinkedBlockingDeque 的 Node 实现多了指向前一个节点的变量 prev,以此实现双向队列。并发控制上和 ArrayBlockingQueue 类似,采用单个 ReentrantLock 来控制并发。因为双端队列头尾都可以消费和生产,所以使用了一个共享锁。

双向阻塞队列可以运用在“工作窃取”模式中。

public class LinkedBlockingDequeTest {

    private static LinkedBlockingDeque<String> DEQUE = new LinkedBlockingDeque<>(2);

    public static void main(String[] args) {
DEQUE.addFirst("java");
DEQUE.addFirst("c++");
// java
System.out.println(DEQUE.peekLast());
// java
System.out.println(DEQUE.pollLast());
DEQUE.addLast("php");
// c++
System.out.println(DEQUE.pollFirst());
}
}

tips: take() 方法调用的是 takeFirst(),使用时候需注意。

PriorityBlockingQueue

PriorityBlockingQueue 是一个底层由数组实现的无界阻塞队列,并带有排序功能。由于是无界队列,所以插入永远不会被阻塞。默认情况下元素采取自然顺序升序排列。也可以自定义类实现 compareTo()方法来指定元素排序规则,或者初始化 PriorityBlockingQueue 时,指定构造参数 Comparator 来对元素进行排序。

底层同样采用 ReentrantLock 来控制并发,由于只有获取会阻塞,所以只采用一个Condition(只通知消费)来实现。

public class PriorityBlockingQueueTest {

    private static PriorityBlockingQueue<String> QUEUE = new PriorityBlockingQueue<>();

    public static void main(String[] args) {
QUEUE.add("java");
QUEUE.add("javaScript");
QUEUE.add("c++");
QUEUE.add("python");
QUEUE.add("php");
Iterator<String> it = QUEUE.iterator();
while (it.hasNext()) {
// c++ javaScript java python php
// 同优先级不保证排序顺序
System.out.print(it.next() + " ");
}
}
}

DelayQueue

DelayQueue 是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实现 Delayed 接口(Delayed 接口的设计可以参考 ScheduledFutureTask 类),元素按延迟优先级排序,延迟时间短的排在前面,只有在延迟期满时才能从队列中提取元素。

DelayQueue 中的 PriorityQueue 会对队列中的任务进行排序。排序时,time 小的排在前面(时间早的任务将被先执行)。如果两个任务的 time 相同,就比较 sequenceNumber,sequenceNumber 小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

和 PriorityBlockingQueue 相似,底层也是数组,采用一个 ReentrantLock 来控制并发。

应用场景:

  1. 缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询 DelayQueue,一旦能从 DelayQueue 中获取元素时,表示缓存有效期到了。
  2. 定时任务调度:使用 DelayQueue 保存当天将会执行的任务和执行时间,一旦从 DelayQueue 中获取到任务就开始执行,比如 TimerQueue 就是使用 DelayQueue 实现的。
public class DelayElement implements Delayed, Runnable {

    private static final AtomicLong SEQUENCER = new AtomicLong();
/**
* 标识元素先后顺序
*/
private final long sequenceNumber;
/**
* 延迟时间,单位纳秒
*/
private long time; public DelayElement(long time) {
this.time = System.nanoTime() + time;
this.sequenceNumber = SEQUENCER.getAndIncrement();
} @Override
public long getDelay(TimeUnit unit) {
return unit.convert(time - System.nanoTime(), NANOSECONDS);
} @Override
public int compareTo(Delayed other) {
// compare zero if same object
if (other == this) {
return 0;
}
if (other instanceof DelayElement) {
DelayElement x = (DelayElement) other;
long diff = time - x.time;
if (diff < 0) {
return -1;
} else if (diff > 0) {
return 1;
} else if (sequenceNumber < x.sequenceNumber) {
return -1;
} else {
return 1;
}
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
} @Override
public void run() {
System.out.println("sequenceNumber" + sequenceNumber);
} @Override
public String toString() {
return "DelayElement{" + "sequenceNumber=" + sequenceNumber + ", time=" + time + '}';
}
}
public class DelayQueueTest {

    private static DelayQueue<DelayElement> QUEUE = new DelayQueue<>();

    public static void main(String[] args) {
// 1. 添加 10 个参数
for (int i = 1; i < 10; i++) {
// 2. 5 秒内随机延迟
int nextInt = new Random().nextInt(5);
long convert = TimeUnit.NANOSECONDS.convert(nextInt, TimeUnit.SECONDS);
QUEUE.offer(new DelayElement(convert));
}
// 3. 查询元素排序 —— 延迟短的排在前面
Iterator<DelayElement> iterator = QUEUE.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 4. 可观察到元素延迟输出
while (!QUEUE.isEmpty()) {
Thread thread = new Thread(QUEUE.poll());
thread.start();
}
}
}

LinkedTransferQueue

LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。

并发控制上采用了大量的 CAS 操作,没有使用锁。

相对于其他阻塞队列,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。

  1. transfer : Transfers the element to a consumer, waiting if necessary to do so. 存入的元素必须等到有消费者消费才返回。
  2. tryTransfer:Transfers the element to a waiting consumer immediately, if possible. 如果有消费者正在等待消费元素,则把传入的元素传给消费者。否则立即返回 false,不用等到消费。

SynchronousQueue

SynchronousQueue 是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则继续 put 操作会被阻塞。Executors.newCachedThreadPool 就使用了这个队列。

SynchronousQueue 默认情况下线程采用非公平性策略访问队列,未使用锁,全部通过 CAS 操作来实现并发,吞吐量非常高,高于 LinkedBlockingQueue 和 ArrayBlockingQueue,非常适合用来处理一些高效的传递性场景。Executors.newCachedThreadPool() 就使用了 SynchronousQueue 进行任务传递。

public class SynchronousQueueTest {

    private static class SynchronousQueueProducer implements Runnable {

        private BlockingQueue<String> blockingQueue;

        private SynchronousQueueProducer(BlockingQueue<String> queue) {
this.blockingQueue = queue;
} @Override
public void run() {
while (true) {
try {
String data = UUID.randomUUID().toString();
System.out.println(Thread.currentThread().getName() + " Put: " + data);
blockingQueue.put(data);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class SynchronousQueueConsumer implements Runnable { private BlockingQueue<String> blockingQueue; private SynchronousQueueConsumer(BlockingQueue<String> queue) {
this.blockingQueue = queue;
} @Override
public void run() {
while (true) {
try {
System.out.println(Thread.currentThread().getName() + " take(): " + blockingQueue.take());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) { final BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
SynchronousQueueProducer queueProducer = new SynchronousQueueProducer(synchronousQueue);
new Thread(queueProducer, "producer - 1").start();
SynchronousQueueConsumer queueConsumer1 = new SynchronousQueueConsumer(synchronousQueue);
new Thread(queueConsumer1, "consumer — 1").start();
SynchronousQueueConsumer queueConsumer2 = new SynchronousQueueConsumer(synchronousQueue);
new Thread(queueConsumer2, "consumer — 2").start();
}
}

 

 

  1. 参考书籍:《Java 并发编程的艺术》
  2. 参考博文:https://www.cnblogs.com/konck/p/9473677.html

多线程编程学习六(Java 中的阻塞队列).的更多相关文章

  1. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  2. 聊聊并发(七)——Java中的阻塞队列

    3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...

  3. JUC之Java中的阻塞队列及其实现原理

    在文章线程池实现原理 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中介绍了线程池的组成部分,其中一个组成部分就是阻塞队列.那么JAVA中的阻塞队列如何实现的呢? 阻塞队列,关键字是阻塞 ...

  4. Java中的阻塞队列(BlockingQueue)

    1. 什么是阻塞队列 阻塞队列(BlockingQueue)是 Java 5 并发新特性中的内容,阻塞队列的接口是 java.util.concurrent.BlockingQueue,它提供了两个附 ...

  5. 阻塞队列一——java中的阻塞队列

    目录 阻塞队列简介:介绍阻塞队列的特性与应用场景 java中的阻塞队列:介绍java中实现的供开发者使用的阻塞队列 BlockQueue中方法:介绍阻塞队列的API接口 阻塞队列的实现原理:具体的例子 ...

  6. Java中的阻塞队列-ArrayBlockingQueue(一)

    最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...

  7. Java中的阻塞队列

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

  8. java 中的阻塞队列

    1.什么是阻塞队列: 支持阻塞的插入方法,意思是当队列满时,队列会阻塞插入元素的线程,知道队列不满. 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空. 插入和移除操作的4种处 ...

  9. 多线程编程学习一(Java多线程的基础).

    一.进程和线程的概念 进程:一次程序的执行称为一个进程,每个 进程有独立的代码和数据空间,进程间切换的开销比较大,一个进程包含1—n个线程.进程是资源分享的最小单位. 线程:同一类线程共享代码和数据空 ...

随机推荐

  1. golang "[]uint8" to string

    关于Uinit8和Byte: The Go Programming Language Specification Numeric types uint8 the set of all unsigned ...

  2. sql上传木马

    第十周笔记—SQLmap和sql注入上传木马 目录 第十周笔记—SQLmap和sql注入上传木马 SQL注入 上传木马 SQLmap 六大模块 命令 参数 三种请求方式 取得系统shell sqlma ...

  3. TIJ学习--RTTI(Runtime-Time Type Identification)

    TIJ学习--RTTI(Runtime-Time Type Identification) RTTI 运行时类型检查机制 获取一个类的Class引用的三种方法 class TestClass{} Te ...

  4. Shell基本语法---shell脚本的输入以及脚本拥有特效地输出

    shell脚本的输入 语法:read -参数 -p:给出提示符.默认不支持"\n"换行 -s:隐藏输入的内容 -t:给出等待的时间,超时会退出read,单位是秒 -n:限制读取字符 ...

  5. 【未解决】iOS QBImagePickerController访问相册没有取消和确定按钮

    这两天调程序时遇到了这个问题,如图所示: 感觉这问题也是奇葩………… 用系统的 UIImagePickerController 替换后就正常了.看来是 QBImagePickerController ...

  6. 记一次idea问题—performing vcs refresh...

    01.前言 本人出现该场景是,我把本地SVN A项目删了,而A项目与B项目同在一个SVN目录下,当我修改B项目且提交代码时,出现了该问题. idea不是很懂操作,就搜索了一下得出了三种答案,但只有其一 ...

  7. .Net Core CLR FileFormat Call Method( Include MetaData, Stream, #~)

    .Net Core  CLR PE 文件启动方法,找到函数入口点,调用整个.Net 程式宿主. 使用方法:可以利用Visual Studio新建一个控制台应用程序,然后生成DLL,替换掉本程序DLL, ...

  8. poj 1050 To the Max(最大子矩阵之和)

    http://poj.org/problem?id=1050 我们已经知道求最大子段和的dp算法 参考here  也可参考编程之美有关最大子矩阵和部分. 然后将这个扩大到二维就是这道题.顺便说一下,有 ...

  9. java并发之ConcurrentLinkedQueue

    在并发编程中,我们可能经常需要用到线程安全的队列,JDK提供了两种模式的队列:阻塞队列和非阻塞队列.阻塞队列使用锁实现,非阻塞队列使用CAS实现.ConcurrentLinkedQueue是一个基于链 ...

  10. 数字麦克风PDM信号采集与STM32 I2S接口应用

    数字麦克风采用MEMS技术,将声波信号转换为数字采样信号,由单芯片实现采样量化编码,一般而言数字麦克风的输出有PDM麦克风和PCM麦克风,由于PDM麦克风结构.工艺简单而大量应用,在使用中要注意这二者 ...