之前因为找实习的缘故,博客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的更多相关文章

  1. java并发包分析之———BlockingQueue

    一.概述: BlockingQueue作为线程容器,可以为线程同步提供有力的保障.   二.BlockingQueue定义的常用方法 1.BlockingQueue定义的常用方法如下:   抛出异常 ...

  2. java并发包分析之———Deque和LinkedBlockingDeque

    一.双向队列Deque   Queue除了前面介绍的实现外,还有一种双向的Queue实现Deque.这种队列允许在队列头和尾部进行入队出队操作,因此在功能上比Queue显然要更复杂.下图描述的是Deq ...

  3. java并发包分析之———AQS框架

    一.什么是同步器   多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx . 这个共同的语义可以称之为同步器.可以认为以上所有的锁机制都可以基 ...

  4. java并发包分析之———concurrentHashMap

    一.Map体系 Hashtable是JDK 5之前Map唯一线程安全的内置实现(Collections.synchronizedMap不算).Hashtable继承的是Dictionary(Hasht ...

  5. java并发包分析之———volitale

    首要结论:volatile 变量提供了线程的可见性,并不能保证线程安全性和原子性. 什么是线程的可见性: 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility ...

  6. java并发包分析之———Atomic类型

    一.何谓Atomic?   Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过 ...

  7. java并发包分析之———ConcurrentSkipListMap

    一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下,ConcurrentHashMap 存取速度是ConcurrentSki ...

  8. Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析

    目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...

  9. Java并发包源码分析

    并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...

随机推荐

  1. JavaScript当页面关闭时向后台发送请求

    今天做项目时遇上一个需求,当浏览器或页面关闭时将数据存储到数据库内.实现思想是采用js监测onunload然后发送请求.结果失败,刷新可以发送但是关闭并不能,整了一整天并没有解决,最后找到了解决办法. ...

  2. 用Java实现将多级文件夹下的所有文件统一放到一个文件夹中

    每次下了电影(男生懂得呦),每部电影都放在一个单独的文件夹里,看的时候很是不方便啊,一直重复着进入文件夹.后退,再进.再退的操作,而手动把这些电影全部复制出来又太繁琐.因此为了解决这个问题,用IO写了 ...

  3. flex中为控件添加监听器并计算

    1.添加监听器: public function moduleCreationComplete():void { this.D601_29a.addEventListener(FlexEvent.SE ...

  4. 通过 dhcp-agent 访问 Metadata - 每天5分钟玩转 OpenStack(168)

    OpenStack 默认通过 l3-agent 创建和管理 neutron-ns-metadata-proxy,进而与 nova-metadata-api 通信.但不是所有环境都有 l3-agent, ...

  5. For循环及例题

    For循环    (1)循环操作某一个功能(执行某段代码)    (2)四要素                  循环初始值                  循环条件                 ...

  6. XML配置文件的命名空间与Spring配置文件中的头

    一直以来,写Spring配置文件,都是把其他配置文件的头拷贝过来,最多改改版本号,也不清楚哪些是需要的,到底是干嘛的.今天整理一下,拒绝再无脑copy. 一.Spring配置文件常见的配置头 < ...

  7. Sass实战 sass官网

    Sass实战 sass官网 1.相关视频教程:http://pan.baidu.com/s/1eSl8bUa 1.1我的项目源码:http://pan.baidu.com/s/1dFmqbyp 1.2 ...

  8. Tcl与Design Compiler (十二)——综合后处理

    本文如果有错,欢迎留言更正:此外,转载请标明出处 http://www.cnblogs.com/IClearner/  ,作者:IC_learner 概述 前面也讲了一些综合后的需要进行的一些工作,这 ...

  9. 【Android 系统开发】CyanogenMod 13.0 源码下载 编译 ROM 制作 ( 手机平台 : 小米4 | 编译平台 : Ubuntu 14.04 LTS 虚拟机)

                 分类: Android 系统开发(5)                                              作者同类文章X 版权声明:本文为博主原创文章 ...

  10. Xcode的Architectures、Valid Architectures和Build Active Architecture Only属性(原创)

    最近xcode升级了5.1版本,升级之后程序报关于要适配arm64机器的错.之前对xcode的参数配置,一直不是很了解,但实现先面对问题了,就调查了一下并解决它. 一个一个来吧. Architectu ...