LinkedBlockingQueue详解
LinkedBlockingQueue介绍
【1】LinkedBlockingQueue是一个基于链表实现的阻塞队列,默认情况下,该阻塞队列的大小为Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限,队列可以随着元素的添加而动态增长,但是如果没有剩余内存,则队列将抛出OOM错误。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。
【2】LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。
LinkedBlockingQueue使用
- //指定队列的大小创建有界队列
- BlockingQueue<Integer> boundedQueue = new LinkedBlockingQueue<>(100);
- //无界队列
- BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();
LinkedBlockingQueue的源码分析
【1】属性值
- // 容量,指定容量就是有界队列
- private final int capacity;
- // 元素数量,用原子操作类的原因在于有两个线程都会操作需要保证可见性
- private final AtomicInteger count = new AtomicInteger();
- // 链表头 本身是不存储任何元素的,初始化时item指向null
- transient Node<E> head;
- // 链表尾
- private transient Node<E> last;
- // take锁 锁分离,提高效率
- private final ReentrantLock takeLock = new ReentrantLock();
- // notEmpty条件
- // 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
- private final Condition notEmpty = takeLock.newCondition();
- // put锁
- private final ReentrantLock putLock = new ReentrantLock();
- // notFull条件
- // 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
- private final Condition notFull = putLock.newCondition();
- //典型的单链表结构
- static class Node<E> {
- E item; //存储元素
- Node<E> next; //后继节点 单链表结构
- Node(E x) { item = x; }
- }
【2】构造函数
- public LinkedBlockingQueue() {
- // 如果没传容量,就使用最大int值初始化其容量
- this(Integer.MAX_VALUE);
- }
- public LinkedBlockingQueue(int capacity) {
- if (capacity <= 0) throw new IllegalArgumentException();
- this.capacity = capacity;
- // 初始化head和last指针为空值节点
- last = head = new Node<E>(null);
- }
- public LinkedBlockingQueue(Collection<? extends E> c) {
- this(Integer.MAX_VALUE);
- final ReentrantLock putLock = this.putLock;
- putLock.lock(); // 为保证可见性而加的锁
- try {
- int n = 0;
- for (E e : c) {
- if (e == null)
- throw new NullPointerException();
- if (n == capacity)
- throw new IllegalStateException("Queue full");
- enqueue(new Node<E>(e));
- ++n;
- }
- count.set(n);
- } finally {
- putLock.unlock();
- }
- }
【3】核心方法分析
1)入队put方法
- public void put(E e) throws InterruptedException {
- // 不允许null元素
- if (e == null) throw new NullPointerException();
- int c = -1;
- // 新建一个节点
- Node<E> node = new Node<E>(e);
- final ReentrantLock putLock = this.putLock;
- final AtomicInteger count = this.count;
- // 使用put锁加锁
- putLock.lockInterruptibly();
- try {
- // 如果队列满了,就阻塞在notFull上等待被其它线程唤醒(阻塞生产者线程)
- while (count.get() == capacity) {
- notFull.await();
- }
- // 队列不满,就入队
- enqueue(node);
- c = count.getAndIncrement();// 队列长度加1,返回原值
- // 如果现队列长度小于容量,notFull条件队列转同步队列,准备唤醒一个阻塞在notFull条件上的线程(可以继续入队)
- // 这里为啥要唤醒一下呢?
- // 因为可能有很多线程阻塞在notFull这个条件上,而取元素时只有取之前队列是满的才会唤醒notFull,此处不用等到取元素时才唤醒
- if (c + 1 < capacity)
- notFull.signal();
- } finally {
- putLock.unlock(); // 真正唤醒生产者线程
- }
- // 如果原队列长度为0,现在加了一个元素后立即唤醒阻塞在notEmpty上的线程
- if (c == 0)
- signalNotEmpty();
- }
- private void enqueue(Node<E> node) {
- // 直接加到last后面,last指向入队元素
- last = last.next = node;
- }
- private void signalNotEmpty() {
- final ReentrantLock takeLock = this.takeLock;
- takeLock.lock();// 加take锁
- try {
- notEmpty.signal();// notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程
- } finally {
- takeLock.unlock(); // 真正唤醒消费者线程
- }
- }
2)出队take方法
- public E take() throws InterruptedException {
- E x;
- int c = -1;
- final AtomicInteger count = this.count;
- final ReentrantLock takeLock = this.takeLock;
- // 使用takeLock加锁
- takeLock.lockInterruptibly();
- try {
- // 如果队列无元素,则阻塞在notEmpty条件上(消费者线程阻塞)
- while (count.get() == 0) {
- notEmpty.await();
- }
- // 否则,出队
- x = dequeue();
- c = count.getAndDecrement();//长度-1,返回原值
- if (c > 1)// 如果取之前队列长度大于1,notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程,原因与入队同理
- notEmpty.signal();
- } finally {
- takeLock.unlock(); // 真正唤醒消费者线程
- }
- // 为什么队列是满的才唤醒阻塞在notFull上的线程呢?
- // 因为唤醒是需要加putLock的,这是为了减少锁的次数,所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程,
- // 这也是锁分离带来的代价
- // 如果取之前队列长度等于容量(已满),则唤醒阻塞在notFull的线程
- if (c == capacity)
- signalNotFull();
- return x;
- }
- private E dequeue() {
- // head节点本身是不存储任何元素的
- // 这里把head删除,并把head下一个节点作为新的值
- // 并把其值置空,返回原来的值
- Node<E> h = head;
- Node<E> first = h.next;
- h.next = h; // 方便GC
- head = first;
- E x = first.item;
- first.item = null;
- return x;
- }
- private void signalNotFull() {
- final ReentrantLock putLock = this.putLock;
- putLock.lock();
- try {
- notFull.signal();// notFull条件队列转同步队列,准备唤醒阻塞在notFull上的线程
- } finally {
- putLock.unlock(); // 解锁,这才会真正的唤醒生产者线程
- }
- }
LinkedBlockingQueue总结
【1】无界阻塞队列,可以指定容量,默认为 Integer.MAX_VALUE,先进先出,存取互不干扰
【2】数据结构:链表(可以指定容量,默认为 Integer.MAX_VALUE,内部类Node存储元素)
【3】锁分离:存取互不干扰,存取操作的是不同的Node对象(takeLock【取Node节点保证前驱后继不乱】,putLock【存Node节点保证前驱后继不乱】,删除时则两个锁一起加)【这是最大的亮点】
【4】阻塞对象(notEmpty【出队:队列count=0,无元素可取时,阻塞在该对象上】,notFull【入队:队列count=capacity,放不进元素时,阻塞在该对象上】)
【5】入队,从队尾入队,由last指针记录。
【6】出队,从队首出队,由head指针记录。
【7】线程池中采用LinkedBlockingQueue而不采用ArrayBlockingQueue的原因便是因为锁分离带来了性能的提升,大大提高队列的吞吐量。
LinkedBlockingQueue详解的更多相关文章
- 20.并发容器之ArrayBlockingQueue和LinkedBlockingQueue实现原理详解
1. ArrayBlockingQueue简介 在多线程编程过程中,为了业务解耦和架构设计,经常会使用并发容器用于存储多线程间的共享数据,这样不仅可以保证线程安全,还可以简化各个线程操作.例如在“生产 ...
- JAVA线程池原理详解二
Executor框架的两级调度模型 在HotSpot VM的模型中,JAVA线程被一对一映射为本地操作系统线程.JAVA线程启动时会创建一个本地操作系统线程,当JAVA线程终止时,对应的操作系统线程也 ...
- ThreadPoolExecutor运转机制详解
ThreadPoolExecutor运转机制详解 - 走向架构师之路 - 博客频道 - CSDN.NET 最近发现几起对ThreadPoolExecutor的误用,其中包括自己,发现都是因为没有仔细看 ...
- java线程池的使用与详解
java线程池的使用与详解 [转载]本文转载自两篇博文: 1.Java并发编程:线程池的使用:http://www.cnblogs.com/dolphin0520/p/3932921.html ...
- 详解Executor框架
在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...
- Java多线程学习之线程池源码详解
0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...
- 【Java多线程】Executor框架的详解
在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...
- Java:多线程,线程池,ThreadPoolExecutor详解
1. ThreadPoolExecutor的一个常用的构造方法 ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepA ...
- Spring cloud Hystrix的配置属性优先级和详解
Hystrix配置属性详解 Hystrix可以配置属性的有以下类型: Execution:控制HystrixCommand.run() 的如何执行 Fallback: 控制HystrixCommand ...
随机推荐
- 使用gulp助力前端自动化
前言 随着前端诸如webpack,rollup,vite的发展,gulp感觉似乎好像被取代了.其实并没有,只不过它从台前退居到了幕后.我们仍然可以在很多项目中看到它的身影,比如elementplus. ...
- 青峰Flutter视频播放软件
下载地址: https://github.com/patton88/peak_flutter_player/raw/master/peak_flutter_player_v1.1.5_release0 ...
- 日均 6000+ 实例,TB 级数据流量,Apache DolphinScheduler 如何做联通医疗大数据平台的“顶梁柱”?
作者 | 胡泽康 鄞乐炜 作者简介 胡泽康 联通(广东)产业互联网公司 大数据工程师,专注于开源大数据领域,从事大数据平台研发工作 鄞乐炜 联通(广东)产业互联网公司 大数据工程师,主要从事大数据平 ...
- docker compose搭建redis7.0.4高可用一主二从三哨兵集群并整合SpringBoot【图文完整版】
一.前言 redis在我们企业级开发中是很常见的,但是单个redis不能保证我们的稳定使用,所以我们要建立一个集群. redis有两种高可用的方案: High availability with Re ...
- 基于Anacoda搭建虚拟环境cudnn6.0+cuda8.0+python3.6+tensorflow-gpu1.4.0
!一定要查准cudnn,cuda,tensorflow-gpu对应的版本号再进行安装,且本文一切安装均在虚拟环境中完成. 下文以笔者自己电脑为例,展开安装教程阐述(省略anaconda安装教程): 1 ...
- 【NOI P模拟赛】仙人掌(圆方树,树形DP)
题面 n n n 个点, m m m 条边. 1 ≤ n ≤ 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 1\leq n\leq 10^5,n-1\leq m\leq 2\times1 ...
- 《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(15)-Fiddler弱网测试,知否知否,应是必知必会
1.简介 现在这个时代已经属于流量时代,用户对于App或者小程序之类的操作界面的数据和交互的要求也越来越高.对于测试人员弱网测试也是需要考验自己专业技术能力的一种技能.一个合格的测试人员,需要额外关注 ...
- 巧用 transition 实现短视频 APP 点赞动画
在各种短视频界面上,我们经常会看到类似这样的点赞动画: 非常的有意思,有意思的交互会让用户更愿意进行互动. 那么,这么有趣的点赞动画,有没有可能使用纯 CSS 实现呢?那当然是必须的,本文,就将巧妙的 ...
- 关于 Math.random()生成指定范围内的随机数的公式推导
关于 Math.random()生成指定范围内的随机数的公式推导 在 java 中,用于生成随机数的 Math 方法 random()只能生成 0-1 之间的随机数,而对于生成指定区间,例如 a-b ...
- 第六十一篇:Vue的绑定事件和修饰符
好家伙,补基础加实践 1.绑定事件 我们使用v-on(简写为@)来绑定事件 写个例子, 按钮绑定数字加一(太tm经典了) 在<button>元素中使用@点击事件绑定方法"的&qu ...