26.jdk源码阅读之ConcurrentLinkedQueue

1. 写在前面

在这里插入图片描述
ConcurrentLinkedQueue 是 Java 中提供的一个基于无界非阻塞算法的线程安全队列。它是一个适用于高并发环境的队列实现,基于链表结构。不知道大家在日常工作中这个类用的多不多,它的一些实现细节原理是否有思考过,比如下面这个几个问题:

  1. ConcurrentLinkedQueue 和 LinkedBlockingQueue 有什么区别?
  2. ConcurrentLinkedQueue 使用了哪些关键技术来实现线程安全?
  3. 如何确保 ConcurrentLinkedQueue 的高并发性能?
  4. ConcurrentLinkedQueue 的典型使用场景有哪些?
  5. ConcurrentLinkedQueue 如何实现无锁的插入和删除操作?
  6. ConcurrentLinkedQueue 的弱一致性是什么?
  7. ConcurrentLinkedQueue 的 size 方法是线程安全的吗?
  8. ConcurrentLinkedQueue 的迭代器是线程安全的吗?

2. 从使用说起

2.1 任务调度

在多线程环境中,使用 ConcurrentLinkedQueue 存储待处理的任务,并由多个工作线程并发地处理这些任务。

import java.util.concurrent.ConcurrentLinkedQueue;

public class TaskScheduler {
    private ConcurrentLinkedQueue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();
    private volatile boolean running = true;

    // 添加任务到队列
    public void addTask(Runnable task) {
        taskQueue.offer(task);
    }

    // 启动工作线程
    public void startWorkers(int numWorkers) {
        for (int i = 0; i < numWorkers; i++) {
            new Thread(() -> {
                while (running) {
                    Runnable task = taskQueue.poll();
                    if (task != null) {
                        task.run();
                    }
                }
            }).start();
        }
    }

    // 停止任务调度
    public void stop() {
        running = false;
    }

    public static void main(String[] args) {
        TaskScheduler scheduler = new TaskScheduler();

        // 添加任务
        scheduler.addTask(() -> System.out.println("Task 1 executed"));
        scheduler.addTask(() -> System.out.println("Task 2 executed"));

        // 启动工作线程
        scheduler.startWorkers(2);

        // 为了演示方便,延迟 5 秒后停止任务调度
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.stop();
    }
}

2.2 事件驱动系统

在事件驱动系统中,使用 ConcurrentLinkedQueue 存储和处理事件。

import java.util.concurrent.ConcurrentLinkedQueue;

public class EventDrivenSystem {
    private ConcurrentLinkedQueue<String> eventQueue = new ConcurrentLinkedQueue<>();
    private volatile boolean running = true;

    // 添加事件到队列
    public void addEvent(String event) {
        eventQueue.offer(event);
    }

    // 处理事件
    public void startEventProcessor() {
        new Thread(() -> {
            while (running) {
                String event = eventQueue.poll();
                if (event != null) {
                    processEvent(event);
                }
            }
        }).start();
    }

    // 处理事件的逻辑
    private void processEvent(String event) {
        System.out.println("Processing event: " + event);
    }

    // 停止事件处理
    public void stop() {
        running = false;
    }

    public static void main(String[] args) {
        EventDrivenSystem system = new EventDrivenSystem();

        // 添加事件
        system.addEvent("Event 1");
        system.addEvent("Event 2");

        // 启动事件处理
        system.startEventProcessor();

        // 为了演示方便,延迟 5 秒后停止事件处理
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        system.stop();
    }
}

2.3 消息队列

分布式系统中,使用 ConcurrentLinkedQueue 实现简单的消息队列。

import java.util.concurrent.ConcurrentLinkedQueue;

public class MessageQueue {
    private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<>();
    private volatile boolean running = true;

    // 生产者线程:添加消息到队列
    public void startProducer() {
        new Thread(() -> {
            int messageCount = 0;
            while (running) {
                String message = "Message " + messageCount++;
                messageQueue.offer(message);
                System.out.println("Produced: " + message);
                try {
                    Thread.sleep(1000); // 模拟生产间隔
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    // 消费者线程:从队列中取出消息并处理
    public void startConsumer() {
        new Thread(() -> {
            while (running) {
                String message = messageQueue.poll();
                if (message != null) {
                    System.out.println("Consumed: " + message);
                }
            }
        }).start();
    }

    // 停止消息队列
    public void stop() {
        running = false;
    }

    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue();

        // 启动生产者和消费者线程
        queue.startProducer();
        queue.startConsumer();

        // 为了演示方便,延迟 10 秒后停止消息队列
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        queue.stop();
    }
}

2.4 生产者-消费者模式

使用 ConcurrentLinkedQueue 实现生产者-消费者模式。

import java.util.concurrent.ConcurrentLinkedQueue;

public class ProducerConsumer {
    private ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
    private volatile boolean running = true;

    // 生产者线程:生成数据并添加到队列
    public void startProducer() {
        new Thread(() -> {
            int count = 0;
            while (running) {
                queue.offer(count++);
                System.out.println("Produced: " + count);
                try {
                    Thread.sleep(500); // 模拟生产间隔
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    // 消费者线程:从队列中取出数据并处理
    public void startConsumer() {
        new Thread(() -> {
            while (running) {
                Integer data = queue.poll();
                if (data != null) {
                    System.out.println("Consumed: " + data);
                }
            }
        }).start();
    }

    // 停止生产和消费
    public void stop() {
        running = false;
    }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        // 启动生产者和消费者线程
        pc.startProducer();
        pc.startConsumer();

        // 为了演示方便,延迟 10 秒后停止生产和消费
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pc.stop();
    }
}

3. add(E e)底层实现

  public boolean add(E e) {
        return offer(e);
    }
 public boolean offer(E e) {
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            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".
                    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)
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

3.1 add 方法

add 方法只是简单地调用 offer 方法。add 方法在 Collection 接口中定义,而 offer 方法在 Queue 接口中定义。两者在 ConcurrentLinkedQueue 中的实现是相同的。

3.2 offer 方法

  1. checkNotNull(e)
    这个方法检查传入的元素是否为 null,如果是 null,则抛出 NullPointerException。ConcurrentLinkedQueue 不允许存储 null 元素。
  2. 创建新节点
final Node<E> newNode = new Node<E>(e);

创建一个新的节点来存储传入的元素。

  1. 循环遍历队列
for (Node<E> t = tail, p = t;;) {
    Node<E> q = p.next;

初始化两个节点引用 t 和 p,都指向队列的尾节点 tail。然后进入无限循环,遍历队列。
4. 检查 p.next

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".
        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
}

如果 q 为 null,说明 p 是当前的尾节点。尝试通过 CAS 操作将 newNode 插入到 p.next。

  • 如果 CAS 操作成功,newNode 成为了队列的新尾节点。
  • 如果 p 不等于 t,说明 tail 已经更新,需要更新 tail 指向 newNode。
  • 返回 true,表示插入成功。
    如果 CAS 操作失败,说明有其他线程已经插入了新节点,需要重新读取 p.next。
  1. 处理尾节点不一致的情况
else if (p == q)
    // We have fallen off list.  If tail is unchanged, it
    // will also be off-list, in which case we need to
    // jump to head, from which all live nodes are always
    // reachable.  Else the new tail is a better bet.
    p = (t != (t = tail)) ? t : head;

如果 p 等于 q,说明我们已经遍历到了队列的末尾。此时需要重新检查 tail,如果 tail 没有变化,则跳转到 head 节点重新开始遍历。
6. 正常遍历队列

else
    // Check for tail updates after two hops.
    p = (p != t && t != (t = tail)) ? t : q;

如果 p.next 不为 null,说明当前节点不是尾节点。继续遍历队列,并在必要时更新 tail。

4. poll()的底层实现

下面这段代码是 ConcurrentLinkedQueue 中的 poll() 方法的实现。poll 方法用于从队列的头部移除并返回一个元素,如果队列为空则返回 null。

    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;
            }
        }
    }

4.1 无限循环和标签

restartFromHead:
for (;;) {
    for (Node<E> h = head, p = h, q;;) {

使用无限循环 for (;😉 来不断尝试移除元素。restartFromHead 是一个标签,用于在某些情况下重新从队列的头部开始遍历。

初始化两个节点引用 h 和 p,都指向队列的头节点 head。

4.2 获取当前节点的元素

E item = p.item;

获取当前节点 p 的元素 item。

4.3 尝试通过 CAS 操作移除元素

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;
}

如果 item 不为 null,说明当前节点 p 存储了一个有效的元素。尝试通过 CAS 操作将 p.item 设置为 null,表示移除该元素。

  • 如果 CAS 操作成功,说明元素已经成功移除。
  • 如果 p 不等于 h,说明 head 已经更新。调用 updateHead 方法更新 head 指向新的头节点 q 或者 p。
  • 返回移除的元素 item。

4.4 处理队列为空的情况

else if ((q = p.next) == null) {
    updateHead(h, p);
    return null;
}

如果 p.next 为 null,说明当前节点 p 是队列的尾节点,队列为空。调用 updateHead 方法更新 head 指向 p,然后返回 null。

4.5 处理尾节点不一致的情况

else if (p == q)
    continue restartFromHead;

如果 p 等于 q,说明我们已经遍历到了队列的末尾。此时需要重新从队列的头部开始遍历,使用 continue restartFromHead 标签跳转到外层循环的开始。

4.6 正常遍历队列

else
    p = q;

如果 p.next 不为 null,说明当前节点不是尾节点。继续遍历队列,将 p 更新为 p.next。

系列文章

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

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至真源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值