ArrayBlockingQueue

底层以数组的结构存放队列元素,容量大小不可改变。

先看下变量:

items:数组,用于存放队列中的元素

takeIndex:获取元素的索引位置

putIndex:存放元素的索引位置

count:队列中当前元素数量

lock:控制队列进出的锁,ArrayBlockingQueue中进出共用一把锁,LinkedBlockingQueue中进出是两把分开的锁

notEmpty:从队列获取元素时的等待条件,也就是队列空时,获取元素的线程会阻塞,直到有线程放元素进来,notEmpty.signal唤醒

notFull:往队列里放元素时的等待条件,也就是队列满时,存放元素的线程会阻塞,直到有线程从队列里取元素并调用notFull.signal唤醒

下面看下代码更直观些:

put

1.先获得锁

2.循环判断当前元素数量是否等于数组长度,也就是队列的容量,等于的话就挂起当前线程,直到notFull.signal

3.插入元素到队列中,代码如下:

putIndex为下一次入队的索引位置,先把元素入队,然后+1后判断是否等于数组长度,如果等于就置位0,从0开始

最后count+1并notEmpty.signal唤醒因为队列为空导致获取元素阻塞的线程。

take

1.获取锁,注意,是和put同一把锁,也就是同时只能有一个线程获得操作队列的权限,无论是入队还是出队。

2.循环判断如果此时队空,就挂起当前线程,等待notEmpty.signal

3.出队:

获取元素并将数组中相应位置置空,设置下次入队索引并count-1,notFull.signal唤醒因为队满而阻塞的线程并返回出队元素

LinkedBlockingQueue

底层以单向链表结构存放队列元素,put和take分别采用两把不同的锁,也就是读写是并发运行的,

那么怎样控制并发环境下的出队和入队呢?答案就是使用AtomicInteger原子操作当前队列中元素数量。

下面看下有哪些实例变量:

capacity:队列容量,可以在构造方法中设置容量,如果不设置的话,默认为Integer.MAX_VALUE

count:当前队列中的元素数量,AtomicInteger类型,因为这里的put和take是两把锁,也就是会并发修改count,所以这里采用原子操作。

head:队头

last:队尾

takeLock:出队锁

notEmpty:出队时,如果队空,就等待notEmpty.signal

putLock:入队锁

notFull:入队时,如果队满,就等待notFull.signal

put

public void put(E e) throws InterruptedException {
     // 因为在出队时如果队空,有些方法是非阻塞或者等待一段时间后返回null的,比如poll
     // 所以禁止往队列中入队null
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对象中
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.
*/
       // 因为count加操作都在putLock中,所以这里可以是==判断
while (count.get() == capacity) {
notFull.await();
}
       // 入队
enqueue(node);
       // 获取原来count值并原子加一
c = count.getAndIncrement();
       // 如果队列未满,通知阻塞线程继续入队
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
     // 如果队列之前是队空状态,说明之前有可能存在take元素的线程因为队空而阻塞,这里通知takeLock唤醒等待线程
if (c == 0)
signalNotEmpty();
}

很简单,就是将刚刚的node链接到队尾

take

1.先获得takeLock

2.如果队空,挂起等待

3.出队

4.获取count值并减一

5.如果队列中还有元素,notEmpty唤醒之前因为队空阻塞的线程

6.如果take之前状态为队满,说明有可能存在因为队满而导致入队阻塞的线程,

唤醒他们(其实这里signal只唤醒一个,在被唤醒的线程完成入队后判断如果还有空间就继续signal,是这样一连串的动作)

出队逻辑如上,保持head的item为null,这里需要说明的是h.next = h自身循环引用也是会被GC的

SynchronousQueue

SynchronousQueue本身不存储元素,put元素时必须同步等待元素被取走。

PriorityBlockingQueue

具有优先级的队列

DelayQueue

具有延时的队列,延时不结束就不能取数据

BlockingQueue的几个实现分析的更多相关文章

  1. java线程池技术(一):ThreadFactory与BlockingQueue

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.ThreadFactory概述以及源码分析 ThreadFactory很简单,就是一个线程工厂也就是负责生产线程的,我们看下ThreadFact ...

  2. Java多线程(六) —— 线程并发库之并发容器

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/19/326527.html 一.ConcurrentMap API 从这一节开始正式进入并发容器 ...

  3. 深入浅出 Java Concurrency (20): 并发容器 part 5 ConcurrentLinkedQueue[转]

    ConcurrentLinkedQueue是Queue的一个线程安全实现.先来看一段文档说明. 一个基于链接节点的无界线程安全队列.此队列按照 FIFO(先进先出)原则对元素进行排序.队列的头部 是队 ...

  4. ArrayBlockingQueue,BlockingQueue分析

    BlockingQueue接口定义了一种阻塞的FIFO queue,每一个BlockingQueue都有一个容量,让容量满时往BlockingQueue中添加数据时会造成阻塞,当容量为空时取元素操作会 ...

  5. Java并发包分析——BlockingQueue

    之前因为找实习的缘故,博客1个多月没有写了.找实习的经历总算告一段落,现在重新更新博客,这次的内容是分析Java并发包中的阻塞队列 关于阻塞队列,我之前是一直充满好奇,很好奇这个阻塞是怎么实现.现在我 ...

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

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

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

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

  8. JDK源码分析(11)之 BlockingQueue 相关

    本文将主要结合源码对 JDK 中的阻塞队列进行分析,并比较其各自的特点: 一.BlockingQueue 概述 说到阻塞队列想到的第一个应用场景可能就是生产者消费者模式了,如图所示: 根据上图所示,明 ...

  9. 并发编程(九)—— Java 并发队列 BlockingQueue 实现之 LinkedBlockingQueue 源码分析

    LinkedBlockingQueue 在看源码之前,通过查询API发现对LinkedBlockingQueue特点的简单介绍: 1.LinkedBlockingQueue是一个由链表实现的有界队列阻 ...

随机推荐

  1. AcWing 838. 堆排序

    #include <iostream> #include <algorithm> using namespace std; ; int n, m; int h[N], size ...

  2. 偏移分页器、游标分页器、Django-filter插件

    复习 """ 1.drf-jwt签发与校验源码 签发token的请求 - 登录请求 - ObtainJSONWebToken - post - 将账号密码丢给序列化类处理 ...

  3. Abaqus 载荷分类(部分)

    目录 1. 集中载荷 1.1 集中载荷施加方法 1.2 定义集中跟随力 1.3 指定文件定义集中节点力 2. 分布载荷 2.1 分布载荷分类 3. 热载荷 3.1 模拟热辐射 3.2 直接定义热流量 ...

  4. 【PAT甲级】1111 Online Map (30分)(dijkstra+路径记录)

    题意: 输入两个正整数N和M(N<=500,M<=N^2),分别代表点数和边数.接着输入M行每行包括一条边的两个结点(0~N-1),这条路的长度和通过这条路所需要的时间.接着输入两个整数表 ...

  5. Linux shell tr 命令详解

    该随笔摘自 https://www.jb51.net/article/103892.htm Linux shell tr 命令详解 1. 用途 tr,translate的简写,主要用于压缩重复字符,删 ...

  6. Go递归函数

    package main import "fmt" func main() { /* 递归函数(recursion):一个函数自己调用自己,就叫做递归函数. 递归函数要有一个出口, ...

  7. knn 算法 k个相近邻居

    # 一个最基本的例子 #样本数据的封装 feature = [[170,70,42],[166,56,39],[188,90,44],[165,88,40],[170,66,40],[176,80,4 ...

  8. 深入delphi编程理解之消息(三)发送消息函数的一般应用实例

    通过对消息函数(SendMessage.PostMessage.Perform)的一般应用,来说明sendmessage.postmessage函数和perform 方法调用方式和结果的区别. 一.程 ...

  9. 混淆-SmartAssembly

    SmartAssembly 7 documentation:https://documentation.red-gate.com/sa SmartAssembly7.2版本下载链接: https:// ...

  10. linux中的diff命令

    今天在公司的代码中看到了一个用的不是很多的命令diff,一开始以为不是,后来一查发现还真有这个命令,有关它的详细资料在这个网址中查看[http://blog.chinaunix.net/uid-253 ...