27.jdk源码阅读之ConcurrentLinkedDeque

1. 写在前面

类继承和实现关系

ConcurrentLinkedDeque 是 Java 中一个高效、线程安全的双端队列(Deque),使用无锁算法(CAS 操作)来保证线程安全性。由于其复杂的实现和广泛的应用场景,它常常成为面试中的重点考察对象。不知道下面几个问题你在面试过程中有没有被问到过?

  1. ConcurrentLinkedDeque与ConcurrentLinkedQueue有什么区别?
  2. ConcurrentLinkedDeque 使用了什么技术来保证线程安全?
  3. 如何实现 ConcurrentLinkedDeque 的无锁插入操作?
  4. ConcurrentLinkedDeque 如何保证队列的一致性?
  5. ConcurrentLinkedDeque 的 pollFirst 方法的实现逻辑是什么?
  6. ConcurrentLinkedDeque 是否支持 null 元素?
  7. 在什么场景下使用 ConcurrentLinkedDeque 比较合适?

2.从使用说起

2.1 基本操作

ConcurrentLinkedDeque 提供了丰富的基本操作,包括在队列两端进行插入、删除和查看操作。以下是一些基本操作的示例:

import java.util.concurrent.ConcurrentLinkedDeque;public class BasicOperations {public static void main(String[] args) {ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();// 在队列头部插入元素deque.addFirst("A");deque.addFirst("B");// 在队列尾部插入元素deque.addLast("C");deque.addLast("D");// 查看头部和尾部元素System.out.println("Head: " + deque.peekFirst()); // BSystem.out.println("Tail: " + deque.peekLast());  // D// 从队列头部移除元素System.out.println("Removed from head: " + deque.pollFirst()); // B// 从队列尾部移除元素System.out.println("Removed from tail: " + deque.pollLast()); // D}
}

2.2 并发生产者-消费者模型

ConcurrentLinkedDeque 非常适合用于生产者-消费者模型,其中多个生产者线程向队列添加任务,多个消费者线程从队列中取出任务进行处理。

import java.util.concurrent.ConcurrentLinkedDeque;public class ProducerConsumer {private static ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();public static void main(String[] args) {// 启动生产者线程Thread producer1 = new Thread(new Producer(), "Producer-1");Thread producer2 = new Thread(new Producer(), "Producer-2");// 启动消费者线程Thread consumer1 = new Thread(new Consumer(), "Consumer-1");Thread consumer2 = new Thread(new Consumer(), "Consumer-2");producer1.start();producer2.start();consumer1.start();consumer2.start();}static class Producer implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {String item = Thread.currentThread().getName() + "-Item-" + i;deque.addLast(item);System.out.println(Thread.currentThread().getName() + " produced " + item);try {Thread.sleep(100); // 模拟生产时间} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}static class Consumer implements Runnable {@Overridepublic void run() {while (true) {String item = deque.pollFirst();if (item != null) {System.out.println(Thread.currentThread().getName() + " consumed " + item);} else {try {Thread.sleep(50); // 队列为空时等待} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}}
}

2.3 双端队列的应用:双向搜索

双端队列可以用于实现双向搜索算法,例如在图搜索中同时从起点和终点进行搜索,直到搜索路径相交。

import java.util.concurrent.ConcurrentLinkedDeque;public class BidirectionalSearch {public static void main(String[] args) {ConcurrentLinkedDeque<Integer> deque = new ConcurrentLinkedDeque<>();// 初始化搜索队列deque.addFirst(0);  // 起点deque.addLast(100); // 终点// 模拟双向搜索while (!deque.isEmpty()) {// 从头部进行搜索Integer head = deque.pollFirst();if (head != null) {System.out.println("Searching from start: " + head);// 假设找到目标if (head == 50) break;// 向队列中添加新的搜索节点deque.addLast(head + 1);}// 从尾部进行搜索Integer tail = deque.pollLast();if (tail != null) {System.out.println("Searching from end: " + tail);// 假设找到目标if (tail == 50) break;// 向队列中添加新的搜索节点deque.addFirst(tail - 1);}}}
}

2.4 任务调度

ConcurrentLinkedDeque 可以用于任务调度系统,支持任务的动态添加和优先级调整。

import java.util.concurrent.ConcurrentLinkedDeque;public class TaskScheduler {private static ConcurrentLinkedDeque<String> taskQueue = new ConcurrentLinkedDeque<>();public static void main(String[] args) {// 添加普通任务taskQueue.addLast("Task1");taskQueue.addLast("Task2");// 添加高优先级任务taskQueue.addFirst("HighPriorityTask");// 处理任务while (!taskQueue.isEmpty()) {String task = taskQueue.pollFirst();System.out.println("Processing " + task);}}
}

2.5 线程安全的双端队列

在多线程环境中,ConcurrentLinkedDeque 可以作为一个线程安全的双端队列,用于存储和处理数据

import java.util.concurrent.ConcurrentLinkedDeque;public class ThreadSafeDeque {private static ConcurrentLinkedDeque<Integer> deque = new ConcurrentLinkedDeque<>();public static void main(String[] args) {// 启动多个线程进行操作Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {deque.addFirst(i);System.out.println("Thread 1 added " + i + " to the front");}});Thread t2 = new Thread(() -> {for (int i = 5; i < 10; i++) {deque.addLast(i);System.out.println("Thread 2 added " + i + " to the end");}});Thread t3 = new Thread(() -> {while (!deque.isEmpty()) {Integer item = deque.pollFirst();if (item != null) {System.out.println("Thread 3 removed " + item + " from the front");}}});t1.start();t2.start();t3.start();}
}

3. addFirst(E e)底层实现原理

下面这段代码是 ConcurrentLinkedDeque 中用于在队列头部插入元素的方法实现,下面我们逐行分析这段代码,解释其工作原理。

  public void addFirst(E e) {linkFirst(e);}
private void linkFirst(E e) {checkNotNull(e);final Node<E> newNode = new Node<E>(e);restartFromHead:for (;;)for (Node<E> h = head, p = h, q;;) {if ((q = p.prev) != null &&(q = (p = q).prev) != null)// Check for head updates every other hop.// If p == q, we are sure to follow head instead.p = (h != (h = head)) ? h : q;else if (p.next == p) // PREV_TERMINATORcontinue restartFromHead;else {// p is first nodenewNode.lazySetNext(p); // CAS piggybackif (p.casPrev(null, newNode)) {// Successful CAS is the linearization point// for e to become an element of this deque,// and for newNode to become "live".if (p != h) // hop two nodes at a timecasHead(h, newNode);  // Failure is OK.return;}// Lost CAS race to another thread; re-read prev}}}

3.1 方法签名

public void addFirst(E e) {linkFirst(e);
}

addFirst(E e) 方法是公开的接口,用于在队列头部插入元素。它调用了私有方法 linkFirst(E e) 来完成实际的插入操作。

3.2 私有方法 linkFirst

private void linkFirst(E e) {checkNotNull(e);final Node<E> newNode = new Node<E>(e);
  1. checkNotNull(e):检查传入的元素是否为 null,如果是 null 则抛出 NullPointerException。
  2. final Node newNode = new Node(e):创建一个新的节点 newNode,其值为 e。

3.3 循环和节点遍历

    restartFromHead:for (;;)for (Node<E> h = head, p = h, q;;) {if ((q = p.prev) != null &&(q = (p = q).prev) != null)// Check for head updates every other hop.// If p == q, we are sure to follow head instead.p = (h != (h = head)) ? h : q;else if (p.next == p) // PREV_TERMINATORcontinue restartFromHead;else {// p is first nodenewNode.lazySetNext(p); // CAS piggybackif (p.casPrev(null, newNode)) {// Successful CAS is the linearization point// for e to become an element of this deque,// and for newNode to become "live".if (p != h) // hop two nodes at a timecasHead(h, newNode);  // Failure is OK.return;}// Lost CAS race to another thread; re-read prev}}
}
  1. restartFromHead::标签,用于在需要时重新从头开始遍历。
  2. for (;😉:无限循环,直到插入操作成功。
  3. for (Node h = head, p = h, q;😉:内部循环,用于遍历节点链表。
  4. if ((q = p.prev) != null && (q = (p = q).prev) != null):尝试向前移动两个节点。如果 p.prev 和 p.prev.prev 都不为 null,则更新 p 和 q。
  5. p = (h != (h = head)) ? h : q:每隔两个节点检查一次头节点 head,如果头节点更新了,则重新设置 p。
  6. else if (p.next == p) // PREV_TERMINATOR:检查是否遇到终止节点(PREV_TERMINATOR),如果是,则重新从头开始遍历。
  7. else:找到了头节点,可以插入新节点。
    • newNode.lazySetNext§:设置新节点的 next 指针指向当前节点 p
    • if (p.casPrev(null, newNode)):使用 CAS 操作将当前节点 p 的 prev 指针从 null 设置为新节点 newNode。如果成功,表示新节点成功插入到队列头部。
    • if (p != h) // hop two nodes at a time:如果当前节点 p 不是头节点 h,则尝试更新头节点。
    • casHead(h, newNode):使用 CAS 操作更新头节点为新节点 newNode。
    • return:插入成功,返回。
  8. // Lost CAS race to another thread; re-read prev:如果 CAS 操作失败,表示有其他线程同时插入了节点,重新读取 prev 指针并重试。

4. addLast(E e)底层实现原理

下面这段代码是 ConcurrentLinkedDeque 中用于在队列尾部插入元素的方法实现。下面我们逐行分析这段代码,解释其工作原理。

 public void addLast(E e) {linkLast(e);}
private void linkLast(E e) {checkNotNull(e);final Node<E> newNode = new Node<E>(e);restartFromTail:for (;;)for (Node<E> t = tail, p = t, q;;) {if ((q = p.next) != null &&(q = (p = q).next) != null)// Check for tail updates every other hop.// If p == q, we are sure to follow tail instead.p = (t != (t = tail)) ? t : q;else if (p.prev == p) // NEXT_TERMINATORcontinue restartFromTail;else {// p is last nodenewNode.lazySetPrev(p); // CAS piggybackif (p.casNext(null, newNode)) {// Successful CAS is the linearization point// for e to become an element of this deque,// and for newNode to become "live".if (p != t) // hop two nodes at a timecasTail(t, newNode);  // Failure is OK.return;}// Lost CAS race to another thread; re-read next}}}

4.1 方法签名

public void addLast(E e) {linkLast(e);
}

addLast(E e) 方法是公开的接口,用于在队列尾部插入元素。它调用了私有方法 linkLast(E e) 来完成实际的插入操作。

4.2 私有方法 linkLast

private void linkLast(E e) {checkNotNull(e);final Node<E> newNode = new Node<E>(e);
  1. checkNotNull(e):检查传入的元素是否为 null,如果是 null 则抛出 NullPointerException。
  2. final Node newNode = new Node(e):创建一个新的节点 newNode,其值为 e。

4.3 循环和节点遍历

    restartFromTail:for (;;)for (Node<E> t = tail, p = t, q;;) {if ((q = p.next) != null &&(q = (p = q).next) != null)// Check for tail updates every other hop.// If p == q, we are sure to follow tail instead.p = (t != (t = tail)) ? t : q;else if (p.prev == p) // NEXT_TERMINATORcontinue restartFromTail;else {// p is last nodenewNode.lazySetPrev(p); // CAS piggybackif (p.casNext(null, newNode)) {// Successful CAS is the linearization point// for e to become an element of this deque,// and for newNode to become "live".if (p != t) // hop two nodes at a timecasTail(t, newNode);  // Failure is OK.return;}// Lost CAS race to another thread; re-read next}}
}
  • restartFromTail::标签,用于在需要时重新从尾部开始遍历。
  • for (;😉:无限循环,直到插入操作成功。
  • for (Node t = tail, p = t, q;😉:内部循环,用于遍历节点链表。
  • if ((q = p.next) != null && (q = (p = q).next) != null):尝试向后移动两个节点。如果 p.next 和 p.next.next 都不为 null,则更新 p 和 q。
  • p = (t != (t = tail)) ? t : q:每隔两个节点检查一次尾节点 tail,如果尾节点更新了,则重新设置 p。
  • else if (p.prev == p) // NEXT_TERMINATOR:检查是否遇到终止节点(NEXT_TERMINATOR),如果是,则重新从尾部开始遍历。
  • else:找到了尾节点,可以插入新节点。
    • newNode.lazySetPrev§:设置新节点的 prev 指针指向当前节点 p。
    • if (p.casNext(null, newNode)):使用 CAS 操作将当前节点 p 的 next 指针从 null 设置为新节点 newNode。如果成功,表示新节点成功插入到队列尾部。
    • if (p != t) // hop two nodes at a time:如果当前节点 p 不是尾节点 t,则尝试更新尾节点。
    • casTail(t, newNode):使用 CAS 操作更新尾节点为新节点 newNode。
    • return:插入成功,返回。
  • Lost CAS race to another thread; re-read next:如果 CAS 操作失败,表示有其他线程同时插入了节点,重新读取 next 指针并重试。

5. pollFirst()的底层实现原理

下面这段代码是 ConcurrentLinkedDeque 中用于从队列头部移除并返回第一个元素的方法实现。面我们逐行分析这段代码,解释其工作原理。

5.1 方法签名

public E pollFirst() {

pollFirst() 方法是公开的接口,用于从队列头部移除并返回第一个元素。如果队列为空,则返回 null。

5.2 方法实现

    for (Node<E> p = first(); p != null; p = succ(p)) {E item = p.item;if (item != null && p.casItem(item, null)) {unlink(p);return item;}}return null;
}
  1. for (Node p = first(); p != null; p = succ§):循环遍历队列中的节点。
    • Node p = first():获取队列的第一个节点。
    • p != null:如果当前节点 p 不为 null,则继续循环。
    • p = succ§:将当前节点 p 更新为其后继节点。
  2. E item = p.item:获取当前节点 p 的元素值 item。
  3. if (item != null && p.casItem(item, null)):检查当前节点的元素值是否不为 null,并尝试使用 CAS 操作将当前节点的元素值从 item 设置为 null。
    • item != null:确保当前节点的元素值不为 null。
    • p.casItem(item, null):使用 CAS 操作将当前节点的元素值从 item 设置为 null。如果成功,表示当前节点的元素值被成功移除。
  4. unlink§:调用 unlink§ 方法,将当前节点 p 从链表中解除链接。
    • unlink§ 方法的作用是将当前节点从链表中移除,具体实现可能包括更新前驱节点和后继节点的指针,以确保链表的完整性。
  5. return item:返回被移除的元素值 item。
  6. return null:如果循环结束且没有找到非空的节点,则返回 null,表示队列为空或者没有可移除的元素。

系列文章

1.JDK源码阅读之环境搭建

2.JDK源码阅读之目录介绍

3.jdk源码阅读之ArrayList(上)

4.jdk源码阅读之ArrayList(下)

5.jdk源码阅读之HashMap

6.jdk源码阅读之HashMap(下)

7.jdk源码阅读之ConcurrentHashMap(上)

8.jdk源码阅读之ConcurrentHashMap(下)

9.jdk源码阅读之ThreadLocal

10.jdk源码阅读之ReentrantLock

11.jdk源码阅读之CountDownLatch

12.jdk源码阅读之CyclicBarrier

13.jdk源码阅读之Semaphore

14.jdk源码阅读之线程池(上)

15.jdk源码阅读之线程池(下)

16.jdk源码阅读之ArrayBlockingQueue

17.jdk源码阅读之LinkedBlockingQueue

18.jdk源码阅读之CopyOnWriteArrayList

19.jdk源码阅读之FutureTask

20.jdk源码阅读之CompletableFuture

21.jdk源码阅读之AtomicLong

22.jdk源码阅读之Thread(上)

23.jdk源码阅读之Thread(下)

24.jdk源码阅读之ExecutorService

25.jdk源码阅读之Executors

26.jdk源码阅读之ConcurrentLinkedQueue

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/50716.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++题解】1069. 字符图形5-星号梯形

问题&#xff1a;1069. 字符图形5-星号梯形 类型&#xff1a;嵌套循环、图形输出 题目描述&#xff1a; 打印字符图形。 输入&#xff1a; 一个整数&#xff08; 0<n<10 &#xff09;。 输出&#xff1a; 一个字符图形。 样例&#xff1a; 输入&#xff1a; 3输…

C#体检系统源码,医院健康体检系统PEIS,C#+VS2016+SQLSERVER

体检中心/医院体检科PEIS系统源码&#xff0c;C#健康体检信息系统源码&#xff0c;PEIS源码 开发环境&#xff1a;C/S架构C#VS2016SQLSERVER 2008 检前&#xff1a; 多种预约方式网站预约、电话预约、微信平台预约及检前沟通&#xff0c;提前制作套餐&#xff0c;客人到达体检…

机器学习(二十三):决策树和决策树学习过程

一、决策树 下面是数据集&#xff0c;输入特征是耳朵形状、脸形状、是否有胡子&#xff0c;输出结果是是否为猫 下图是决策树&#xff0c;根据耳朵形状、脸形状、是否有胡子这几个特征&#xff0c;建立决策树&#xff0c;从根节点一步步预测结果。 上图中&#xff0c;每一个椭…

wkt格式文件详解(包含应用示例)

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

揭秘CISA:不只是证书,更是信息安全领域的国际通行证

CISA&#xff08;Certified Information Systems Auditor&#xff09;&#xff0c;即国际注册信息系统审计师&#xff0c;是信息系统审计、控制与安全等专业领域中备受认可的认证。它不仅是一张证书&#xff0c;更是信息安全领域的国际通行证。以下是对CISA的全面揭秘&#xff…

Apollo:目录分析, test ok

apollo: Apollo (阿波罗)是一个开放的、完整的、安全的平台,将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统,快速搭建一套属于自己的自动驾驶系统。 - Gitee.comhttps://github.com/ApolloAuto/apolloapollo 目录名称目录作用cyber消息中间件,替换ros作为消息层…

Vscode报错:line too long (84 > 79 characters)

原因&#xff1a;不允许一行超过79个字母&#xff0c;但是该行代码超出该范围。 参考博客&#xff1a;解决Vs CodeFlake8 报错line too long (108 &#xff1e; 79 characters)Flake8(E501)_flake8 line too long-CSDN博客

Javascript前端面试基础(八)

window.onload和$(document).ready区别 window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行$(document).ready()是DOM结构绘制完毕后就执行&#xff0c;不必等到加载完毕 window.onload 触发时机&#xff1a;window.onload 事件会在整个页面&#xf…

微服务面试-分布式 注册中心 远程调用 保护

标红的原理还是不太熟悉 重新看 分布式事务 CAP理论 Consistency&#xff08;一致性&#xff09; Availability&#xff08;可用性&#xff09; Partition tolerance &#xff08;分区容错性&#xff09; BASE 理论 就是做取舍 cap三选二 AT模式脏写 TCC模式 注册中…

2024年7月29日(web nginx)

web 一、web基本概念和常识 Web:为用户提供的一种在互联网上浏览信息的服务,Web服务是动态的、可交互的、跨平台的和图形化的。 Web 服务为用户提供各种互联网服务,这些服务包括信息浏览服务,以及各种交互式服务,包括聊天、购物、学习等等内容。 Web 应用开发也经过了几代技术…

MySQL 执行计划详解

文章目录 一. 概念二. 语法三. 详解各字段1. id2. select_type3. table4. partitions5. type6. possible_keys与key7. key_len8. ref9. rows10. filtered11. Extra 一. 概念 有了慢查询后&#xff0c;需要对慢查询语句进行分析。一条查询语句经过MySQL查询优化器后&#xff0c…

最新 【Navicat Premium 17.0.8】简体中文版破解激活永久教程

官方下载地址&#xff1a; https://www.navicat.com.cn/download/navicat-premium 百度网盘补丁链接 链接: https://pan.baidu.com/s/11hu414Honi3Y9dPQ6-07JQ?pwd04mu 提取码: 04mu 未安装过的用户可直接跳过该步骤&#xff0c;如果已安装Navicat&#xff0c;记得先卸载干净…

阿里云主机 安装RabbitMQ

一、操作系统 用的是Alibaba Cloud Linux release 3 (Soaring Falcon)系统&#xff0c;可以通过命令&#xff1a;lsb_release -a 查看系统信息。 二、安装RabbitMQ RabbitMQ 是基于 Erlang 语言构建的&#xff0c;要安装RabbitMQ&#xff0c;需先安装Erlang环境。通过Erlang V…

【图解网络】学习记录

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 TCP/IP 网络模型有哪几层&#xff1f;键入网址到网页显示&#xff0c;期间发生了什么&#xff1f;Linux 系统是如何收发网络包的&#xff1f;NAPIHTTP 是什么&#…

Win10出现错误代码0x80004005 一键修复指南

对于 Windows 10 用户来说&#xff0c;错误代码 0x80004005 就是这样一种迷雾&#xff0c;它可能在不经意间出现&#xff0c;阻碍我们顺畅地使用电脑。这个错误通常与组件或元素的缺失有关&#xff0c;它可能源自注册表的错误、系统文件的损坏&#xff0c;或者是软件的不兼容。…

PyTorch 的 .pt 文件是什么?以及都能存储什么样的数据格式和复合数据格式?加载 train.pt 文件的一个代码示例

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、PyTorch 的 .pt 文件是什么&#xff1f; .pt 文件的基本概念&#xff1a; .pt 文件是 PyTorch 中特有的一种文件格式&#xff0c;用于保存和加载各类数据。.pt为 PyTorch 的缩写。此文件格式极其灵…

dotnet-starter-kit:一个Web API+Blazor多租户、模块化、简洁DDD架构!

推荐一个Web APIBlazor多租户、模块化、简洁DDD项目框架。 01 项目简介 dotnet-starter-kit是一个基于 .NET 8 的开源项目&#xff0c;架构构建基于 Clean Architecture 原则的解决方案。支持多租户、模块化&#xff0c;一个开箱即用的项目&#xff0c;方便我们快速开发项目。…

GitEval — 预测你的 GitHub 个人资料的质量

使用机器学习来预测你是否擅长编码 可直接在橱窗里购买&#xff0c;或者到文末领取优惠后购买&#xff1a; 如果你曾经申请过技术职位&#xff0c;你可能已经向公司发送了你的 GitHub 个人资料链接。此个人资料中的信息可以很好地表明你的编码能力以及是否适合团队。所有这些信…

Spring事件机制

文章目录 一、Spring事件二、实现Spring事件1、自定义事件2、事件监听器2.1 实现ApplicationListener接口2.2 EventListener2.3 TransactionalEventListener 3、事件发布4、异步使用 三、EventBus1、事件模式2、EventBus三要素3、同步事件3.1 定义事件类3.2 定义事件监听3.3 测…

[tomato]靶机复现漏洞详解!

靶机地址&#xff1a; https://download.vulnhub.com/tomato/Tomato.ova 靶机环境&#xff1a;Vmware 网络&#xff1a;NAT模式 信息收集&#xff1a; arp-scan -l 扫描靶机ip地址 扫描开放的端口信息 nmap -sS -sV -p- 192.168.77.135 发现开放端口21&#xff…