LinkedBlockingQueue多线程安全的保障

相信看过我的ArrayBlockingQueue的博客,对于我们分析LinkedBlockingQueue会有一定的帮助,这两个阻塞队列也可以作为我们阻线程池中阻塞对列的选择。

LinkedBlockingQueue采用的是带头节点链表的数据结构。
我们来通过这个类的成员后具体的代码来进行分析LinkedBlockingQueue所采用的实现多线程安全的手段是什么。

public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//用来表示链表的容量大小,如果用户不进行设置的话,那么 capacity = Integer.MAX_VALUE   private final int capacity;//这个AtomicInteger 类中有一个重要的成员private volatile int value;//采用这个 AtomicInteger 中的value进行记录当前元素的数量,// AtomicInteger 中对value值进行更改的时候,采用的是CAS进行更改,//所以在 LinkedBlockingQueue中调用AtomicIntege的方法虽value进行更改的话是可以保证线程安全的,因为1.value采用类volatile保证了其值的可见性,//2是因为采用CAS的形式对其值进行更改,保证其原子性。 private final AtomicInteger count = new AtomicInteger();//用来记录头节点    transient Node<E> head;//用来记录尾结点private transient Node<E> last;//对于LinkedBlockingQueue来说我们采用的是两个ReentrantLock 的对象//分别为takeLock 和 putlock,将添加和拿出的操作进行区分。//notEmpty 为 takeLock的阻塞队列。//notFull  为 putlock的阻塞队列private final ReentrantLock takeLock = new ReentrantLock();private final Condition notEmpty = takeLock.newCondition();private final ReentrantLock putLock = new ReentrantLock();private final Condition notFull = putLock.newCondition();

从上边的成员的介绍,你们可能知道了,关于记录阻塞队列中的元素个数,我们知道是线程安全的因为采用了Volitile和CAS,才保证的count个数具有准确定,
但你们一定很迷惑,为什么head和last这两个成员没有任何的修饰,虽然count的准确性保证了,但对于头节点和尾结点的准确性是如何保障的?

我们都知道在进行删除或添加的时候,都需要对头节点和尾结点进行更改
那我们举个例子,我们采用普通的链表的删除和添加的方法

当一个线程想要删除的时候,那么会将head的值先copy一份,然后当线程的时间片段到了,而此时刚好有个线程想要添加,那么此时这个线程会先将tail的值copy一份,然后时间片段到了,想要删除的线程抢占到cpu资源开始执行删除操作,那么将会使头节点的后驱节点设为null,那就说明tail这个节点不会再被引用了,然后当删除线程进行删除完后,添加节点的线程继续添加,因为添加线程已经将tail的值copy一份,所以线程还是一句当前的tail的值进行添加,那么这个tail还是没有删除前的那个tail,此时线程添加的时候,将会添加到tail的后面,那么此时这个阻塞队列就会成为这个局面,由于tail节点不再被引用导致添加的新节点也不会被引用。导致真正内存中的队列只剩下一个头节点,把本来应该要加入的新节点丢失了。

但LinkedBlockingQueue中采用的节点的删除法是将头节点的后驱节点中中的内容进行清除,然后将头节点删除,将头节点的后驱节点作为删除后的头节点,这样的话,对于就不会造成上述所说的节点缺失的问题,

这个问题是解决了,但是对于不同的线程来说,都保存的是head或者last的复制本,并且也是对其复制本进行操作,会带来一个问题就是最终内存中的head和last的值回因为多线程的问题,导致内存中的head和last不准确

那么LinkedBlockingQueue采取的措施是什么呢。
针对上边的成员来说,我们看到有两个Reentrantlock锁,一个是takelock一个是putlock,那么LinkedBlockingQueue就是采用这两个锁来保证线程安全的。
我们来看看添加操作和删除操作,只展示部分代码说明问题

public boolean offer(E e) {final ReentrantLock putLock = this.putLock;putLock.lock();//采用的是putlocktry {if (count.get() < capacity) {enqueue(node);c = count.getAndIncrement();if (c + 1 < capacity)notFull.signal();}} finally {putLock.unlock();}}

put()

 public void put(E e) throws InterruptedException {final ReentrantLock putLock = this.putLock;final AtomicInteger count = this.count;putLock.lockInterruptibly();//采用的是putlockwhile (count.get() == capacity) {notFull.await();}enqueue(node);c = count.getAndIncrement();if (c + 1 < capacity)notFull.signal();} finally {putLock.unlock();}

我们从上边的方法就可以看出来,对于添加操作,采用的是putlock锁,
那不用说对于删除操作采用的是takelock,
我们再来看put和offer两个方法调用的核心方法enqueue(node);
发现这个方法只有一行有效代码,并且只对last节点进行了操作

private void enqueue(Node<E> node) {// assert putLock.isHeldByCurrentThread();// assert last.next == null;last = last.next = node;}

poll和take方法代用的核心方法我们同样发现了,只用了head节点,或者说只对head节点进行操作。

private E dequeue() {Node<E> h = head;Node<E> first = h.next;h.next = h; // help GChead = first;E x = first.item;first.item = null;return x;}

那我们来总结一下成员head和last为什么没有任何关于线程安全的修饰,同样可以保证多线程安全呢
因为对于LinkedBlockingQueue来说,添加操作采用了putlock锁来保证添加操作的同步,同时又因为添加操作只是对last进行修改,那可以说明last这个变量具有准确性
。同样删除操作采用takelock锁才保证删除操作的同步,同时删除只对head变量进行操作,所以head也是具有准确性的。

LinkedBlockingQueue多线程安全的保障 —————— 开开开山怪相关推荐

  1. 一文让你彻底了解多线程

    伙伴们很抱歉,因为最近需要粉丝突破1000,所以很多文章都设置了仅粉丝可见,如果大家看完这篇文章感觉对自己没啥帮助,可以在取消关注!!! 本文篇幅很长,建议大家分段阅读,如果你准备面试,那么就请你一定 ...

  2. 并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法

    转载自https://blog.csdn.net/westos_linux/article/details/78968012 在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就 ...

  3. python多线程填写体温(一次可填写10+位+含双验证码填写)----升级至尊版

    继上次教程:利用python制作自动填写体温程序最详细教程来了(有后续哦) {注意:代码已经无法运行,仅做参考} 需要完整代码的同学看目录自取,也可以加强学习,大家一起学习交流呀! 温馨提示:本代码仅 ...

  4. 字节二面 | 26图揭秘线程安全

    想必都知道线程是什么,也知道怎么用了,但是使用线程的时候总是没有达到自己预期的效果,要么是值不对,要么是无限等待,为了尽全力的避免这些问题以及更快定位所出现的问题,下面我们看看线程安全的这一系列问题 ...

  5. java编写布局文件_鸿蒙OS利用JAVA编写的布局实践练习

    鸿蒙OS利用JAVA编写的布局实践练习 鸿蒙OS利用JAVA编写的布局实践练习 目录 JAVA UI框架 利用JAVA代码实现一个简单的布局 利用xml实现上述布局 JAVA UI框架 ??应用的Ab ...

  6. Java面试题-个人笔记

    参考:https://thinkwon.blog.csdn.net/article/details/104390612 一.Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java D ...

  7. 即将实习的应届毕业生 学习java SpringMVC 数据库 知识总结

    即将实习的应届毕业生 学习java SpringMVC 数据库 知识总结 1.Java语言的优点: 1)Java是纯面向对象语言 2)与平台无关性,一次编译到处运行 3)Java提供了狠多内置类库 4 ...

  8. iOS 锁的底层原理

    @synchronized(互斥锁) 原理 1.clang分析实现原理 {objc_sync_enter(_sync_obj);try {// 结构体struct _SYNC_EXIT {// 构造函 ...

  9. 高并发必学的 CAS 操作,看这篇就够了!

    大家好,我是树哥. CAS 操作是高并发场景下,性能如此之高的一个重要优化.今天推荐胜哥的一篇关于 CAS 的文章,带你了解 CAS 的前世今生,写得真是太棒了! 背景 在高并发的业务场景下,线程安全 ...

  10. 70道Java开发面试题及答案,linux内核驱动开发视频课程

    线程安全:HashMap时单线程安全的,Hashtable是多线程安全的. 遍历不同:HashMap仅支持Iterator的遍历方式,Hashtable支持Iterator和Enumeration两种 ...

最新文章

  1. 如何安装vscode网页版_Windows10专业版/企业版如何安装Microsoft store
  2. 数据结构之线性存储结构
  3. VHDL中的转换函数
  4. 哈工大LTP本地安装及python调用
  5. wzplayer for android V1.0快出炉了
  6. python中bool函数的作用_Python内置bool函数详细介绍
  7. 判断字符串是否由纯数字组成
  8. Github1.3万星,迅猛发展的JAX对比TensorFlow、PyTorch
  9. 【elasticsearch】Elasticsearch 空值处理实战
  10. 通过零拷贝进行有效的数据传输(java、c)
  11. 重拾阅读--朝花夕拾啊
  12. 计算机软件乘除,基于单片机的智能计算机程序 可以实现加减乘除运算
  13. MySQL参数max_connect_errors分析释疑
  14. Carson带你学Android:这是一份全面 详细的Android代码命名规范
  15. Mac 系统 Arduino IDE 找不到开发板端口的解决方法
  16. Ztmao主题猫wordpress主题经典失传版/WP网站模板下载站源码+全局SEO功能设定
  17. Adobe After Effect (AE) cc2020 安装教程【64位】
  18. 利用SMB协议实现局域网内设备文件的共享
  19. python利用百度/高德地图获取地理位置并转换
  20. 数据资产管理体系方案

热门文章

  1. 解决阿里oss远程图片html2canvas生成海报时跨域问题(附代码)
  2. Tomcat 如何生成SSL安全证书(拜读、学习、记录) and 如何用OpenSSl生成服务端证书 other 简述cer和crt后缀的证书的区别
  3. 线性代数之——正定矩阵
  4. python情绪识别_使用百度对话情绪识别api分析文本
  5. Spark从SQL的解析、执行与调优到Sparksql的解析的史上最全介绍
  6. HCNA 实验指南(Ensp V350)
  7. Linux挂载新硬盘与格式化数据盘和查看磁盘格式
  8. Tensorflow1.7+cuda9.0+cudnn7.0中的各种意(da)外(keng)
  9. DB2日期函数DATE函数
  10. android模拟器没反应,Android模拟器无法正常工作