Java并发包分析——BlockingQueue
之前因为找实习的缘故,博客1个多月没有写了。找实习的经历总算告一段落,现在重新更新博客,这次的内容是分析Java并发包中的阻塞队列
关于阻塞队列,我之前是一直充满好奇,很好奇这个阻塞是怎么实现。现在我们先看一个该抽象类的实现类ArrayBlockingQueue。下面全部的代码均在github
ArrayBlockingQueue
ArrayBlockingQueue顾名思义是一种数组形式的阻塞队列,其自然就有数组的特点,即队列的长度不可改变,只有初始化的时候指定。
下面,我们看一下例子。
public class ArrayBlock {
private BlockingQueue<String> blockingQueue;
public ArrayBlock(){
blockingQueue = new ArrayBlockingQueue<String>(3);
}
public BlockingQueue<String> getBlockingQueue() {
return blockingQueue;
}
}
创建一个大小为3的ArrayBlockingQueue,下面是一个生产者和消费者,通过ArrayBlockingQueue实现生产者/消费者模型。
public class Producer extends Thread {
private BlockingQueue<String> blockingQueue;
@Override
public void run() {
super.run();
for (int i = 0 ; i < 5;i++) {
try {
blockingQueue.put(i + "");
System.out.println(getName() + " 生产数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Producer(ArrayBlock arrayBlock){
this.setName("Producer");
blockingQueue = arrayBlock.getBlockingQueue();
}
}
public class Costumer extends Thread{
private BlockingQueue<String> blockingQueue;
public Costumer(ArrayBlock arrayBlock) {
blockingQueue = arrayBlock.getBlockingQueue();
this.setName("Costumer");
}
@Override
public void run() {
super.run();
while (true) {
try {
Thread.sleep(6000);
String str = blockingQueue.take();
System.out.println(getName() + " 取出数据 " + str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试过程就不放了,直接放出结果:
Producer 生产数据
Producer 生产数据
Producer 生产数据
Costumer 取出数据 0
Producer 生产数据
Costumer 取出数据 1
Producer 生产数据
Costumer 取出数据 2
Costumer 取出数据 3
Costumer 取出数据 4
这可以看出put方法与take方法均是阻塞的方法。当队列已经满的时候,就会阻塞放入方法,当队列为空的时候,就会阻塞取出方法。
下面,我们主要看这个两个方法,究竟是如何实现阻塞的。
** put方法 **
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
put方法是将元素放入到队列中,这里面可以看出是用过Lock类与Condition类来实现的,即通过等待/通知机制实现的阻塞队列。这里notFull是一个条件,当队列已经满的时候,就会执行await方法,如果没有满就执行入队(enqueue)方法。这里,判断队列已满用的是count == items.length。接下来,我们看一下take方法,来看看取数据的阻塞。
** take方法**
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
这里,与put方法类似,当元素为0时,就会执行await方法,上面方法中都没有直接说明signal方法的执行。其实该方法是入队与出队的方法中实现的。也就是当执行notFull.await()时,是通过dequeue()方法来通知停止等待的,可以放入元素。当执行到notEmpty.await()时,是通过enqueue来通知结束阻塞,可以取出元素。
LinkedBlockingQueue
LinkedBlockingQueue顾名思义是一个链表形式的阻塞队列,不同于ArrayBlockingQueue。如果不指定容量,则默认是Integer.MAX_VALUE。也就是说他是一个无界阻塞队列。他的例子与上面的类似,但是其put与take方法实现不同于ArrayBlockingQueue,但两者大致思路一致。我们只看一下put实现:
** put方法**
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
这里阻塞的本质实现也是通过Condition类的等待/通知机制。但是有几点不同:
第一 这里用了一个原子类的count计数,官方的给的注释是即使没有锁来提供保护,也能保证线程安全,实现wait guard。
第二 ArrayBlockingQueue的通知是在入队与出队的方法中,LinkedBlockingQueue则不是,并且插入之后不满的时候,还有通知其他await的线程。
第三 ArrayBlockingQueue的lock一直是一个,也就是put/take是用的一个锁,放与取无法实现并行。但是LinkedBlockingQueue是两个锁,放一个锁,取一个锁,可以实现put/take的并行,要高效一些。
SynchronousQueue
SynchronousQueue顾名思义是同步队列,特点不同于上面的阻塞队列,他是一个无界非缓存的队列,准确说他不存储元素,放入的元素,只有等待取走元素之后才能放入。也就是说任意时刻:
- isEmpty()法永远返回是true
- remainingCapacity() 方法永远返回是0
- remove()和removeAll() 方法永远返回是false
- iterator()方法永远返回空
- peek()方法永远返回null
元素并不会被生产者存在队列中,而是直接生产者与消费者进行交互。
其实现是利用无锁算法,可以参考SynchronousQueue实现
还有一点需要注意,同步队列支持公平性与非公平性。公平性是利用队列来管理多余生产者与消费者,非公平性是利用栈来管理多余生产者与消费者。
Java并发包分析——BlockingQueue的更多相关文章
- java并发包分析之———BlockingQueue
一.概述: BlockingQueue作为线程容器,可以为线程同步提供有力的保障. 二.BlockingQueue定义的常用方法 1.BlockingQueue定义的常用方法如下: 抛出异常 ...
- java并发包分析之———Deque和LinkedBlockingDeque
一.双向队列Deque Queue除了前面介绍的实现外,还有一种双向的Queue实现Deque.这种队列允许在队列头和尾部进行入队出队操作,因此在功能上比Queue显然要更复杂.下图描述的是Deq ...
- java并发包分析之———AQS框架
一.什么是同步器 多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx . 这个共同的语义可以称之为同步器.可以认为以上所有的锁机制都可以基 ...
- java并发包分析之———concurrentHashMap
一.Map体系 Hashtable是JDK 5之前Map唯一线程安全的内置实现(Collections.synchronizedMap不算).Hashtable继承的是Dictionary(Hasht ...
- java并发包分析之———volitale
首要结论:volatile 变量提供了线程的可见性,并不能保证线程安全性和原子性. 什么是线程的可见性: 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility ...
- java并发包分析之———Atomic类型
一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过 ...
- java并发包分析之———ConcurrentSkipListMap
一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下,ConcurrentHashMap 存取速度是ConcurrentSki ...
- Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析
目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...
- Java并发包源码分析
并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...
随机推荐
- spring或springmvc自动生成applicationcontext.xml或springmvc文件(此文转载和借鉴多篇文章)
在用spring或者springmvc框架进行开发时,编辑applicationcontext.xml等配置文件是必不可少的,在eclipse中打开applicationcontext.xml通常是这 ...
- MyEclipse10的正确破解方法
无法转载,故给出原文链接,以供需要者. MyEclipse10的正确破解方法
- C# const和readonly修饰符的区别
const 的概念就是一个包含不能修改的值的变量.常数表达式是在编译时可被完全计算的表达式.因此不能从一个变量中提取的值来初始化常量.如果 const int a = b+1;b是一个变量,显然不能再 ...
- Zeppelin interperter 模式设置总结
如有错漏,望请指正,不胜感激. 参考:[zeppelin官网]:https://zeppelin.apache.org/docs/latest/interpreter/spark.html#inter ...
- dellR720重启找不到启动引导项,手动选择也无用。
机器重启后显示 no boot device available.(如下图)检查bios中设置也是没问题的,因为装完系统后根本没动过什么.F11手动选择启动项也还是会跳到这里来. 这台机子做的Raid ...
- 机器学习:形如抛物线的散点图在python和R中的非线性回归拟合方法
对于样本数据的散点图形如函数y=ax2+bx+c的图像的数据, 在python中的拟合过程为: ##最小二乘法 import numpy as np import scipy as sp import ...
- MyBatis使用statementType="STATEMENT"
statementType="STATEMENT"是使用非预编译,现在我需要动态传人表名和字段名,所以需要用STATEMENT,使用之后所有变量取值都要改成${xxxx},而不是# ...
- 【Egret】Wing3发布移动APP功能,打包APK流程以及会遇到的问题
流程:1.安装好wing 3,然后 插件--Egret项目工具--发布移动APP--Build android native project 2.配置发布环境: Android 1.egret eng ...
- 使用cocapods报错 [!] Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not use TextEdit for editing it. If you are not using TextEdit, you should turn off smart quotes
从github上下载的工程大部分都使用了cocapods,在install的时候可能会报错. 报错原因: 1.不要使用文本编辑去编辑Podfile文件,使用Xcode编辑,或者使用终端敲命令去编辑. ...
- jQ层级选择器
<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title> ...