参考资料:http://blog.csdn.net/chenchaofuck1/article/details/51660521

实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,阻塞队列就是通过使用加锁的阻塞算法实现的;另一种非阻塞的实现方式则可以使用循环CAS(比较并交换)的方式来实现。

ConcurrentLinkedQueue是一个基于链表实现的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。默认情况下head节点存储的元素为空,tair节点等于head节点。

一:入队

  • 入队主要做两件事情,

第一是将入队节点设置成当前队列的最后一个节点。

第二是更新tail节点,如果原来的tail节点的next节点不为空,则将tail更新为刚入队的节点(即队尾结点),如果原来的tail节点(插入前的tail)的next节点为空,则将入队节点设置成tail的next节点(而tial不移动,成为倒数第二个节点),所以tail节点不总是尾节点!

public boolean offer(E e) {
if (e == null) throw new NullPointerException();
//入队前,创建一个入队节点
Node</e><e> n = new Node</e><e>(e);
retry: //死循环,入队不成功反复入队。 for (;;) { //创建一个指向tail节点的引用 Node</e><e> t = tail; //p用来表示队列的尾节点,默认情况下等于tail节点。 Node</e><e> p = t; //获得p节点的下一个节点。 Node</e><e> next = succ(p); //next节点不为空,说明p不是尾节点,需要更新p后在将它指向next节点 if (next != null) { //循环了两次及其以上,并且当前节点还是不等于尾节点 if (hops > HOPS && t != tail) continue retry;
p = next; }
//如果p是尾节点,则设置p节点的next节点为入队节点。
else if (p.casNext(null, n)) {
//如果tail节点有大于等于1个next节点,则将入队节点设置成tair节点,更新失败了也没关系,因为失败了表示有其他线程成功更新了tair节点。 if (hops >= HOPS) casTail(t, n); // 更新tail节点,允许失败 return true; } // p有next节点,表示p的next节点是尾节点,则重新设置p节点 else {
p = succ(p);
} } }
}

二:出队

不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head节点里的元素,而不会更新head节点。只有当head节点里没有元素时,则弹出head的next结点并更新head结点为原来head的next结点的next结点。

public E poll() {
Node</e><e> h = head;
// p表示头节点,需要出队的节点
Node</e><e> p = h; for (int hops = 0;; hops++) {
// 获取p节点的元素
E item = p.getItem();
// 如果p节点的元素不为空,使用CAS设置p节点引用的元素为null,如果成功则返回p节点的元素。
if (item != null && p.casItem(item, null)) {
if (hops >= HOPS) {
//将p节点下一个节点设置成head节点
Node</e><e> q = p.getNext();
updateHead(h, (q != null) ? q : p);
}
return item;
}
// 如果头节点的元素为空或头节点发生了变化,这说明头节点已经被另外一个线程修改了。那么获取p节点的下一个节点
Node</e><e> next = succ(p); // 如果p的下一个节点也为空,说明这个队列已经空了
if (next == null) {
// 更新头节点。
updateHead(h, p);
break;
} // 如果下一个元素不为空,则将头节点的下一个节点设置成头节点 p = next;
}
return null;
}

三:非阻塞却线程安全的原因

观察入队和出队的源码可以发现,无论入队还是出队,都是在死循环中进行的,也就是说,当一个线程调用了入队、出队操作时,会尝试获取链表的tail、head结点进行插入和删除操作,而插入和删除是通过CAS操作实现的,而CAS具有原子性。故此,如果有其他任何一个线程成功执行了插入、删除都会改变tail/head结点,那么当前线程的插入和删除操作就会失败,则通过循环再次定位tail、head结点位置进行插入、删除,直到成功为止。也就是说,ConcurrentLinkedQueue的线程安全是通过其插入、删除时采取CAS操作来保证的。不会出现同一个tail结点的next指针被多个同时插入的结点所抢夺的情况出现。

Java并发容器之非阻塞队列ConcurrentLinkedQueue的更多相关文章

  1. 多线程高并发编程(11) -- 非阻塞队列ConcurrentLinkedQueue源码分析

    一.背景 要实现对队列的安全访问,有两种方式:阻塞算法和非阻塞算法.阻塞算法的实现是使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出队和入队各一把锁LinkedBloc ...

  2. (原创)JAVA阻塞队列LinkedBlockingQueue 以及非阻塞队列ConcurrentLinkedQueue 的区别

    阻塞队列:线程安全 按 FIFO(先进先出)排序元素.队列的头部 是在队列中时间最长的元素.队列的尾部 是在队列中时间最短的元素.新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素.链接 ...

  3. 9.并发包非阻塞队列ConcurrentLinkedQueue

    jdk1.7.0_79  队列是一种非常常用的数据结构,一进一出,先进先出. 在Java并发包中提供了两种类型的队列,非阻塞队列与阻塞队列,当然它们都是线程安全的,无需担心在多线程并发环境所带来的不可 ...

  4. 25、Java并发性和多线程-阻塞队列

    以下内容转自http://ifeve.com/blocking-queues/: 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操 ...

  5. Java并发编程()阻塞队列和生产者-消费者模式

    阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到有空间可用:如果队列为空,那么take方法将会阻塞直到有元素可用.队列可以 ...

  6. 和朱晔一起复习Java并发(二):队列

    和朱晔一起复习Java并发(二):队列 老样子,我们还是从一些例子开始慢慢熟悉各种并发队列.以看小说看故事的心态来学习不会显得那么枯燥而且更容易记忆深刻. 阻塞队列的等待? 阻塞队列最适合做的事情就是 ...

  7. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  8. java阻塞队列与非阻塞队列

    在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...

  9. 第六章 Java并发容器和框架

    ConcurrentHashMap的实现原理与使用 ConcurrentHashMap是线程安全且高效的hashmap.本节让我们一起研究一下该容器是如何在保证线程安全的同时又能保证高效的操作. 为什 ...

随机推荐

  1. 吸血鬼猎人巴菲第一至八季/全集Buffy迅雷下载

    本季看点:<吸血鬼猎人巴菲>故事背景在现代,话说于每一个世代都会出现一个年青的女孩子,在人世间寻找及对付一些妖魔鬼怪,例如有吸血鬼.坏女巫等等邪恶的势力,而这个年青的女孩子则被称为Slay ...

  2. Lua的文件操作

    先简单介绍一下被迫使用Lua的IO的情境: 游戏支持玩家自定义上传头像,在排行榜中会显示玩家列表(包括本服.跨服),原有的做法是先检测CCUserDefault中是否存在指定图片的key以及它的状态. ...

  3. 低版本系统兼容的ActionBar(二)ActionProvider+分离式ActionBar+分离式的ActionMode

           这篇文章主要讲的是在低版本兼容的ActionBar中实现自定义的ActionProvider,ShareActionProvider的使用方法,如何实现分离式ActionBar,外加在分 ...

  4. Eclipse SDK Android Studio 下载地址

    https://developer.android.com/sdk/index.html#download 这个网址可以下载需要的东西,FQ的话可以给 xifulinmen@gmail.com 发一个 ...

  5. 修改服务的运行权限,解决SVN Post Commit问题

    先来看一下错误现象 之前在Windows Server中安装了VirtualSVN Server,然后设置了Post Commit Hooks,在提交代码到版本库之后,将代码check到本地的一个Se ...

  6. 14 道 JavaScript 题?

    http://perfectionkills.com/javascript-quiz/ https://www.zhihu.com/question/34079683 著作权归作者所有.商业转载请联系 ...

  7. 启明星手机版安卓android会议室预定系统 V1.0发布

    启明星手机版会议室预定系统 V1.0发布 在手机里输入 http://www.dotnetcms.org/e4.apk 或者扫描二维码下载 用户打开系统,可以实时查看所有会议室状态 点击会议室名称,可 ...

  8. POJO与PO、VO的区别

    http://www.cnblogs.com/wangjunwei/p/3859360.html POCO的概念是从java的POJO借用而来,而两者的含义是一致的,不同的仅仅是使用的语言不一样.所以 ...

  9. 案例导入和导出Scott用户

    ylbtech-Oracle:案例导入和导出Scott用户  导入和导出Scott用户 1. 导出Scott用户下的所有对象返回顶部 1.1, Microsoft Windows [版本 ] 版权所有 ...

  10. Go语言之进阶篇实现并发聊天功能

    1.并发聊天服务器原理分析 2.并发聊天室 功能: 广播消息.广播上线. 查询在线用户.修改用户名.用户主动退出.超时处理 示例: package main import ( "fmt&qu ...