阻塞队列 - java基于链表的简单实现
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基于链表的简单实现的更多相关文章
- 特殊的阻塞队列 - java.util.concurrent.SynchronousQueue 分析
描述 SynchrounousQueue 是一个比较特殊的无界阻塞队列并支持非公平和公平模式,严格意义上来说不算一个队列,因为它不像其他阻塞队列一样能有容量,它仅有一个指向栈顶的地址,栈中的节点由线程 ...
- java基于socket的简单聊天系统
/*=============服务端================*/ /** * 服务器程序 在9999端口监听 * 可以通过控制台输入来回应客户端* @author xiaoluo* @qq 3 ...
- 用Java如何设计一个阻塞队列,然后说说ArrayBlockingQueue和LinkedBlockingQueue
前言 用Java如何设计一个阻塞队列,这个问题是在面滴滴的时候被问到的.当时确实没回答好,只是说了用个List,然后消费者再用个死循环一直去监控list的是否有值,有值的话就处理List里面的内容.回 ...
- java并发:阻塞队列
第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...
- java并发包——阻塞队列BlockingQueue及源码分析
一.摘要 BlockingQueue通常用于一个线程在生产对象,而另外一个线程在消费这些对象的场景,例如在线程池中,当运行的线程数目大于核心的线程数目时候,经常就会把新来的线程对象放到Blocking ...
- Java并发(十八):阻塞队列BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ...
- Java并发编程-阻塞队列
Java concurrent 包中BlockingQueue接口有ArrayBlockingqueue.LinkedBlockingQueue.PriorityBlockingQueue.Synch ...
- Java -- 使用阻塞队列(BlockingQueue)控制线程通信
BlockingQueeu接口是Queue的子接口,但是它的主要作用并不是作为容器,而是作为线程同步的工具. 特征: 当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程 ...
- Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析
目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...
随机推荐
- PowerShell自定义函数定义及调用
PowerShell是一种命令集,也有自己的语法定义及函数.本文主要介绍如何自定义powershell函数及如何调用,当初在写PowerShell自定义函数的时候查阅了很多资料都没找到如何调用自定义函 ...
- C语言学习——bsmap-2.74_main.cpp
素材路径:https://www.codeforge.cn/read/428275/bsmap-2.74-_-main.cpp__html 1.C/C++预处理指令,常见的预处理指令如下: #空指令, ...
- jquery AJAX数据传输路径写法~
$.post('{:url("index/index/logininfo")}',{'username':name,'password':pwd},function(data){ ...
- Elasticsearch Windows下安装及配置集群
首先打开网址:https://www.elastic.co/cn/ 进入如下页面: 下载: 解压: 进入bin文件夹下,运行bat文件: 成功后打开浏览器输入地址: 安装head插件: 首先安装nod ...
- oralce问题
死锁,如果较多使用存储过程杀死 create or replace procedure killer is v_obj varchar2(200); v_sql varchar2(500) ...
- [ Servlet / JSP ] J2EE Web Application 中的 JSESSIONID 是什么?
JSESSIONID is a cookie in J2EE web application which is used in session tracking. Since HTTP is a st ...
- VS2017 MVC Spring net 环境配置
首先打开管理NuGet程序包. 搜索 "spring web",安装Spring.Web,Spring.Web.Mvc5,Spring.Web.Extensions, 搜索Micr ...
- libcrypto.so.1.0.0: no version information available
openssl-1.0.1p源码安装后,依赖于openssl.so库的应用报错libcrypto.so.1.0.0: no version information available 解法:1. 创建 ...
- vue eventBus使用
类似于iframe之间的possMessage方式传参 1.eventBus.js文件 //用于兄弟组件通信 import Vue from 'vue'; export default new Vue ...
- logback Filter LevelFilter ThresholdFilter
LevelFilter: 级别过滤器,根据日志级别进行过滤.如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志.有以下子节点: <level>:设置 ...