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. Gradle 简记

    不是 Gradle,就是 Maven吧.对比下: Maven: 推荐(?)了一个默认的项目结构和生命周期,但是太过死板 虽然暴露了 API 接口,但是插件定制太过复杂 和 Ant 一样,仍然无法表达复 ...

  2. 防cc攻击策略

    黑客攻击你的网站,会采取各种各样的手段,其中为了降低你网站的访问速度,甚至让你的服务器瘫痪,它会不断的刷新你的网站,或者模拟很多用户同一时间大量的访问你的网站, 这就是所谓的CC攻击,这就需要我们在程 ...

  3. SSH应用实战——安全防护(fail2ban)

    ssh 安全配置 端口 ssh随机端口范围在 27000-30000,可以手动修改也要改在这个范围内,建议定时修改端口. 密码 登陆密码应包含大小写.数字.特殊字符等 10 位以上,建议定期修改密码. ...

  4. sqlite3如何判断一个表是否已经存在于数据库中 C++

    SELECT count(*) AS cnt FROM sqlite_master WHERE type='table' AND name='table_name';cnt will return 0 ...

  5. MySQL 存储过程 if语句

    MySQL  存储过程 if语句 MySQL IF语句允许您根据表达式的某个条件或值结果来执行一组SQL语句. 要在MySQL中形成一个表达式,可以结合文字,变量,运算符,甚至函数来组合.表达式可以返 ...

  6. 亚马逊VE账号运营

    VE劲爆内幕大揭秘!“仿牌+Amazon VE”跟卖之路 Amazon Vendor Express 是Amazon.com2015年下旬推出的新的供应商平台,商家通过这个平台可以把产品卖给Amazo ...

  7. numpy 数组索引数组

    在numpy中,数组除了可以被整数索引,还可以被数组索引. a[b]就是已数组b的元素为索引,读取数组a的值. 当被索引数组a是一维数组,b是一维或则多维数组时,结果维度维度与索引数组b相同. a = ...

  8. PHP的json_encode()函数与JSON对象

    一.问题描述 这周搬砖的时候,前端通过ajax获取后端的数据后,照例用 对象.属性 的方式取值,然而结果总是总是不能如预期般展示在页面上. 先写个 demo 还原下场景:选中一个下拉框列表选项后,会在 ...

  9. vue-计算属性和侦听器

    1.计算属性 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护.例如: <div id="example"> { ...

  10. 刷题upupup【Java中HashMap、HashSet用法总结】

    HashMap: 常用操作 1. containsKey() 判断HashMap是否包含key 2. containsValue() 判断HashMap是否包含“值为value”的元素 3. get( ...