1、阻塞队列的原理

阻塞队列与普通队列的区别在于:阻塞队列为空时,从队列中获取元素的操作将会被阻塞,当队列为满时,往队列里添加元素的操作会被阻塞。

试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来

2、阻塞队列的简单实现

/**
* 基于链表实现的一个阻塞队列
*
* @author jade
*
*/
public class BlockingQueue {
private int capacity ; // 阻塞队列容量
private List queue = new LinkedList(); // 基于链表实现的一个阻塞队列 public BlockingQueue() {
this(Integer.MAX_VALUE);
} public BlockingQueue(int capacity) {
this.capacity = capacity;
} /**
* 入队列
*
* @param item
* @throws InterruptedException
*/
public synchronized void enqueue(Object item) throws InterruptedException {
while (this.queue.size() == this.capacity) {
wait();
}
if (this.queue.size() == 0) {
notifyAll();
}
this.queue.add(item);
} /**
* 出队列
*
* @return
* @throws InterruptedException
*/
public synchronized Object dequeue() throws InterruptedException {
while (this.queue.size() == 0) {
wait();
}
if (this.queue.size() == this.capacity) {
notifyAll();
}
return this.queue.remove(0);
} }

注意:

1)在enqueue和dequeue方法内部,只有队列的大小等于上限(capacity)或者下限(0)时,才调用notifyAll方法,小于时不调用。

如果队列的大小既不等于上限,也不等于下限,任何线程调用enqueue或者dequeue方法时,都不会阻塞,就不需要唤醒,都能够正常的往队列中添加或者移除元素。

2)enqueue和dequeue方法都加了synchronized ,在进入synchronized的时候获取锁,退出的时候释放锁。而如果没有synchronized,直接使用wait/notify时,无法确认哪个锁。

3、LinkedBlockingQueue

在java.util.concurrent包下提供了若干个阻塞队列,其中LinkedBlockingQueue基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。

首先看一下LinkedBlockingQueue类中的几个成员变量:

  private static final long serialVersionUID = -6903933977591709194L;
private final int capacity;
private final AtomicInteger count = new AtomicInteger();
transient Node<E> head;
private transient Node<E> last;
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = this.takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = this.putLock.newCondition();

可以看出,LinkedBlockingQueue中用来存储元素的实际上是一个链表,head和last分别表示链表的头节点和下一节点, capacity表示队列的容量。

takelock,putLock 是可重入锁,notEmpty和notFull是等待条件。

下面看一下LinkedBlockingQueue的构造器,构造器有三个重载版本:

  public LinkedBlockingQueue()
{
this(Integer.MAX_VALUE);
} public LinkedBlockingQueue(int paramInt)
{
if (paramInt <= 0) {
throw new IllegalArgumentException();
}
this.capacity = paramInt;
this.last = (this.head = new Node(null));
} public LinkedBlockingQueue(Collection<? extends E> paramCollection)
{
this(Integer.MAX_VALUE);
ReentrantLock localReentrantLock = this.putLock;
localReentrantLock.lock();
try
{
int i = 0;
Iterator localIterator = paramCollection.iterator();
while (localIterator.hasNext())
{
Object localObject1 = localIterator.next();
if (localObject1 == null) {
throw new NullPointerException();
}
if (i == this.capacity) {
throw new IllegalStateException("Queue full");
}
enqueue(new Node(localObject1));
i++;
}
this.count.set(i);
}
finally
{
localReentrantLock.unlock();
}
}

第一个构造器默认容量是Integer.MAX_VALUE,第二个构造器只有一个参数用来指定容量,第三个构造器可以指定一个集合进行初始化。

然后看它的两个关键方法的实现:put()和take():

  public void put(E paramE)
throws InterruptedException
{
// 确保放入的元素是非空的 
if (paramE == null) {
throw new NullPointerException();
}
int i = -1;
Node localNode = new Node(paramE);
ReentrantLock localReentrantLock = this.putLock;
AtomicInteger localAtomicInteger = this.count;
// 响应中断 
localReentrantLock.lockInterruptibly();
try
{
while (localAtomicInteger.get() == this.capacity) {
this.notFull.await(); // 阻塞
}
enqueue(localNode);
i = localAtomicInteger.getAndIncrement();
if (i + 1 < this.capacity) {
this.notFull.signal(); ///使用了signal()提高性能, put的时候对notFull 条件队列中阻塞的线程进行唤醒。
}
}
finally
{
localReentrantLock.unlock();
}
if (i == 0) {
signalNotEmpty(); // put 的时候,会在i == 0 的情况下对 notEmpty 条件队列中阻塞的线程进行唤醒,但是这个需要获取takeLock锁。
}
}
public E take()
throws InterruptedException
{
int i = -1;
AtomicInteger localAtomicInteger = this.count;
ReentrantLock localReentrantLock = this.takeLock;
localReentrantLock.lockInterruptibly();
Object localObject1;
try
{
while (localAtomicInteger.get() == 0) {
this.notEmpty.await();
}
localObject1 = dequeue();
i = localAtomicInteger.getAndDecrement();
if (i > 1) {
this.notEmpty.signal();
}
}
finally
{
localReentrantLock.unlock();
}
if (i == this.capacity) {
signalNotFull();
}
return (E)localObject1;
}

跟put方法实现很类似,只不过put方法等待的是notFull信号,而take方法等待的是notEmpty信号

LinkedBlockingQueue内部维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

LinkedBlockingQueue内部使用ReentrantLock实现插入锁(putLock)和取出锁(takeLock)。putLock上的条件变量是notFull,即可以用notFull唤醒阻塞在putLock上的线程。takeLock上的条件变量是notEmtpy,即可用notEmpty唤醒阻塞在takeLock上的线程。

参考了如下博客:

http://www.cnblogs.com/moonandstar08/p/4893337.html

http://www.cnblogs.com/dolphin0520/p/3932906.html

阻塞队列 - java基于链表的简单实现的更多相关文章

  1. 特殊的阻塞队列 - java.util.concurrent.SynchronousQueue 分析

    描述 SynchrounousQueue 是一个比较特殊的无界阻塞队列并支持非公平和公平模式,严格意义上来说不算一个队列,因为它不像其他阻塞队列一样能有容量,它仅有一个指向栈顶的地址,栈中的节点由线程 ...

  2. java基于socket的简单聊天系统

    /*=============服务端================*/ /** * 服务器程序 在9999端口监听 * 可以通过控制台输入来回应客户端* @author xiaoluo* @qq 3 ...

  3. 用Java如何设计一个阻塞队列,然后说说ArrayBlockingQueue和LinkedBlockingQueue

    前言 用Java如何设计一个阻塞队列,这个问题是在面滴滴的时候被问到的.当时确实没回答好,只是说了用个List,然后消费者再用个死循环一直去监控list的是否有值,有值的话就处理List里面的内容.回 ...

  4. java并发:阻塞队列

    第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...

  5. java并发包——阻塞队列BlockingQueue及源码分析

    一.摘要 BlockingQueue通常用于一个线程在生产对象,而另外一个线程在消费这些对象的场景,例如在线程池中,当运行的线程数目大于核心的线程数目时候,经常就会把新来的线程对象放到Blocking ...

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

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

  7. Java并发编程-阻塞队列

    Java concurrent 包中BlockingQueue接口有ArrayBlockingqueue.LinkedBlockingQueue.PriorityBlockingQueue.Synch ...

  8. Java -- 使用阻塞队列(BlockingQueue)控制线程通信

    BlockingQueeu接口是Queue的子接口,但是它的主要作用并不是作为容器,而是作为线程同步的工具. 特征: 当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程 ...

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

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

随机推荐

  1. krpano 常用标签

    <krpano></krpano>根标签 相当于 <body> <scene></scene>一个全景图场景 <image> 图 ...

  2. Linux系统安装笔记

    1.下载CentOS系统镜像: 很多资料都是CentOS6的,7的比较少,所以我决定还是用CentOS6来学习. 地址:http://vault.centos.org/6.8/isos/x86_64/ ...

  3. 【centos7】 Failed to start LSB Bring up/down

    今天碰到几个问题: 1. Failed to start LSB Bring up/down 归结一下 百度 的答案: 1). 90% 修改MAC 地址 2).10% 重启 networkmanage ...

  4. Docker Swarm集群中部署Traefik负载均衡器

    一.创建单节点的Docker Swarm集群 docker swarm init 二.在Swarm集群中创建一个网络 docker network create --driver=overlay tr ...

  5. 实验十一 团队项目设计完善&编码测试

    任务一: 根据OOD详细设计工作要点,修改完善团队项目系统设计说明书和详细设计说明,将完善后内容以<XX团队项目软件设计方案>为名发布在团队博客中,博文中要突出对完善内容的陈述说明,修改部 ...

  6. 环境准备—之—linux下安装svn--开机自启--及format权限问题

    借鉴自 http://www.cnblogs.com/liuxianan/p/linux_install_svn_server.html 这个很详细,但不是我的风格 https://blog.csdn ...

  7. ubuntu终端

    https://blog.csdn.net/hello_new_life/article/details/75099249 ctrl+altl+F1进入命令行 ctrl+altl+F7退出命令行 在终 ...

  8. Phonics 自然拼读法 ai, oa,ie, ee,or,j Teacher:Lamb

    课上内容(Lesson) 1. “L” 的介绍 Light L      e.g.   Love Like Life Dark L      e.g.   Apple  world  call 2. ...

  9. python运用PIL制作GIF

    与一.安装Pillow 安装地址:https://pypi.org/project/Pillow/#files 二.准备好图片,并从0开始命名,如下图: (ps:记得存图位置与新建的py文件在同一存放 ...

  10. gpu相关

    1.查看cuda版本 cat /usr/local/cuda/version.txt 2.查看cudnn版本 cat /usr/local/cuda/include/cudnn.h | grep CU ...