并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理
· 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列。另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas算法来保证线程安全的,接下来就让我们来看一下jdk中两种队列的实现方式。
1. ConcurrentLinkedQueue的实现原理
顾名思义,这是一个基于链表结构的队列,它是一个先进先出的队列,当我们添加元素时,添加的元素链接到队列的尾部,当获取元素时返回队列的头部元素。
先看添加队列时ConcurrentLinkedQueue的实现方法offer():
public boolean offer(E e) {
//判断元素是否为空,为空则抛出异常
checkNotNull(e);
//创建一个新的节点 Node中的结构为item(数据) next(下一个节点)
final Node<E> newNode = new Node<E>(e);
//自旋,将节点添加到队列尾部
for (Node<E> t = tail, p = t;;) {
//获取到p的下一个节点,即是当前tail节点的的下一个节点
Node<E> q = p.next;
//如果q为空,表示q的下一个节点为null,直接将当前节点添加到队列尾部
if (q == null) {
// p is last node
//添加节点到队列尾部
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
//第一次进来时p == t,所以这里不会将当前节点设置成tail
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
//多线程操作时候,由于poll时候会把老的head变为自引用,然后head的next变为新head,所以这里需要 //重新找新的head,因为新的head后面的节点才是激活的节点p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
首先检查当前进来的元素是否为null,为null则抛出空指针异常。将tail赋值给t和p,无限循环,将当前p.next()赋值给q,如果为空,则将当前节点添加到队列尾部,添加成功则继续看p 是否等于t,第一次进来时是相等的,所以不会调用casTail()方法,直接返回true即可,因为在多线程的环境下,可能会出现线程进入循环时,q不等于空,此时看p == q是否成立,开始必然是不成立的,执行最后一个else,将tal赋值给t,并将q赋值给p,继续循环,此时p的next为空,则执行将节点添加到队列尾部的操作,并且此时的p 不等于t,更新尾节点tail位置为当前新添加的节点。
出队方法poll():
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
先来个死循环,再来一个死循环将head赋值给h,h 赋值给p, 得到头节点的值保存到item中,当item不为空时,通过cas算法将p节点的item设置为null,设置成功后,判断p是否等于h,等于则更新头节点,并将p.next()赋值给q,当p.next不为空时,头节点设置为q.否则设置为p,如果p.next()为null,则将更新头节点,返回null,如果p==q,自引用了,则重新找新的头节点。
ConcurrentLinkedQueue主要就是利用了cas算法来保证了多线程环境下线程的安全,这样的算法其实性能来说是比较优的,速度相比较阻塞式的算法会更好一些。
2. BlockingQueue
BlockingQueue是一个阻塞式队列,当tack()时队列为空,则它不会返回空或者是抛异常,线程此时会一直等待着,知道队列中存在数据时才会去取出数据,同时put()当队列满了的情况下,也会等待,知道其他线程取出队列中的数据,腾出空间之后再执行入队操作,其实它也提供了add/remove这样的非阻塞式方法的,当队列full或队列为空时,直接抛出异常,这里我们主要说的是它阻塞的情况,主要有put/take()方法。
这里以BlockingQueue的实现类ArrayBlockingQueue源码进行分析。
put():
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
其实这里的实现原理就是生产者与消费者使用Condition实现原理一样,notFull和notEmpty两个Condition,使用可中断锁,count作为元素个数标记,以一个数组来保存元素,当count等于数组长度时,使notFull等待,否则调用insert()方法添加元素。
insert()方法:
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
这里很简单,将元素保存到数组中,count++,唤醒等待读取元素的线程,告诉它已经有数据了,可以获取了。
take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
使用中断锁,当count为0时,表示当前队列中已经没有资源了,所以线程等待,否则就调用extract()返回数据。
extract():
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
得到takeIndex位置的元素,保存到x中,并且将下表位置的元素置为空,更改takeIndex,count--,唤醒可能由于队列满了的情况下被等待的添加元素的线程,返回x,这样就取到了当前的元素。
这里的实现原理跟生产者/消费者模式一样,同时使用Condition来指定唤醒某些等待的线程,实现了多线程下队列的阻塞和线程安全。
原文 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理
并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理的更多相关文章
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
- Java并发编程学习笔记
Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...
- JUC并发编程学习笔记
JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...
- 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理
在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...
- 并发编程学习笔记(5)----AbstractQueuedSynchronizer(AQS)原理及使用
(一)什么是AQS? 阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架, ...
- 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理
1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...
- 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理
(一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...
- 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题
再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁. 什么是内置锁? 在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对 ...
- 并发编程学习笔记(15)----Executor框架的使用
Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...
随机推荐
- web 开发之js---js 多线程编程
AJAX 开发中的难题 试试多线程编程 想了解更多 有关作者 转载注明出处:http://www.infoq.com/cn/articles/js_multithread 虽然有越来越多的网站在应 ...
- 告诉大家我是如何在14:00秒杀到 《深入理解Bootstrap》
1.打开火狐,不用IE,3个评论窗口,层叠在一起,可以点击3次哦. 2.打开一个百度的现在时间,不能看你本机的时间,要互联网的时间. 3.等时间到13:59:59,开始依次点击按钮,总有你预想不到的结 ...
- [RK3288][Android6.0] 调试笔记 --- 如何确认声卡是否注册成功【转】
本文转载自:http://blog.csdn.net/kris_fei/article/details/78399875 Platform: RK3288 OS: Android 6.0 Kernel ...
- pyhon-----安装yaml踩过的坑以及正解
之前在网上找了各种资料,cmd安装yaml,网上大部分写的都是pip install yaml 可是,执行完就变成Could not find a version that satisfies the ...
- ubuntu 12.04.5 LTS版本 更新 source.list
更新后一定要:apt-get update # # deb cdrom:[Ubuntu-Server LTS _Precise Pangolin_ - Release amd64 (20140806. ...
- JsonFormat和DateTimeFormate格式化参数
JsonFormat :出参 DateTimeFormate : 入参 http://www.iteye.com/problems/53816 @DateTimeFormat(pattern = &q ...
- mysql数据库中的十进位是什么意思?
一般在用小数的时候才有用,比如类型你设置了double,十进位你设为2,那么你可以放0.22的值,但是放0.222的值它会自动四舍五入为0.22,相当于小数位数吧
- 探寻宝藏 --- 双线DP
双线DP , 在郑轻的时候 做过 这种双线DP , 这是多维DP 应该是比较简单的 但是那个 时间复杂度的优化 始终看不懂 . 先附上代码吧 , 等看懂了再来 , 补充一下 解释 . #in ...
- 洛谷 P3372 【模板】线段树 加法
题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个 ...
- Linux上安装禅道
linux一键安装包内置了apache, php, mysql这些应用程序,只需要下载解压缩即可运行禅道. 从7.3版本开始,linux一键安装包分为32位和64位两个包,请大家根据操作系统的情况下载 ...