·  在并发编程中,我们有时候会需要使用到线程安全的队列,而在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(阻塞队列)原理的更多相关文章

  1. 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理

    1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...

  2. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  3. JUC并发编程学习笔记

    JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...

  4. 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理

    在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...

  5. 并发编程学习笔记(5)----AbstractQueuedSynchronizer(AQS)原理及使用

    (一)什么是AQS? 阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架, ...

  6. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

  7. 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

  8. 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题

    再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁. 什么是内置锁? 在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对 ...

  9. 并发编程学习笔记(15)----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

随机推荐

  1. android studio 0.8.1使用和遇到问题解决

    谷歌6月底公布了五大系统,而且android studio同步升级到了android studio 0.8.1.升级了的android studio确实有一些新的变化.执行速度变快,而且还能够选择开发 ...

  2. VBS 操作Word

    VBS 操作Word   1.新建Word文档 '使用Add方法 Dim ObjWD,ObjDOC Set ObjWD=CreateObject("Word.application" ...

  3. Javascript基本概念梳理

    javascript里的数据类型: 原始类型:数字,字符串.布尔值.(原始值:null,undefined) 对象类型:键值对,数组,function,全局对象(MATH,JSON) 保留字: abs ...

  4. P2P网贷中的4种理財业务模式

     线上3种   直投标:线上理財人直接购买借款人的标.平台仅仅是起个"撮合"作用.收点借款人的服务费.           借款人不还钱,有的平台会帮"借款人" ...

  5. JS重名解决方案

    一个页面如果引用多个JS,或者像ASP.NET MVC,一个视图包含多个子视图,每个子视图有自己的JS,那么变量.函数的重名冲突机会将会大增. 如何解决? 这里有一个方案: 1.用类来封装子页的JS代 ...

  6. 在js中取选中的radio值

    在js中取选中的radio值 <input type="radio" name="address" value="0" /> & ...

  7. 利用JFreeChart生成时序图(6) (转自 JSP开发技术大全)

    利用JFreeChart生成时序图(6) (转自 JSP开发技术大全) 14.6 利用JFreeChart生成时序图 通过JFreeChart插件只能生成普通效果的时序图,利用工厂类ChartFact ...

  8. git删除远程分支和本地分支以及更改本地和分支名字

    问题描述: 当我们集体进行项目时,将自定义分支push到主分支master之后,如何删除远程的自定义分支呢 问题解决: (1)使用命令git branch -a 查看所有分支 注: 其中,remote ...

  9. npm更换为淘宝镜像源

    1.通过config命令   1 2 npm config set registry http://registry.cnpmjs.org npm info underscore (如果上面配置正确这 ...

  10. [Swift通天遁地]一、超级工具-(11)使用EZLoadingActivity制作Loading加载等待动画

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...