ConcurrentLinkedQueue详解

ConcurrentLinkedQueue详解

简介

实现线程安全的队列的两种方式:

  1. 使用阻塞算法:对入队和出队使用同一把锁(或不同的锁).
  2. 使用非阻塞算法:使用循环CAS方式实现.
  • ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列.
  • 采用先进先出规则对节点排序.
  • 添加元素会添加到队列的尾部,获取元素会返回队列头部元素.

结构

  • 有两个结点:头节点(head)和尾节点(tail).
  • 每个节点由结点元素(item)和指向下一个节点的指针(next)组成.
  • 默认情况下head节点存储的元素为空,tail节点等于head节点.

操作

入队列

将入队节点添加到队列尾部.

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) {
            if (p.casNext(null, newNode)) {
                if (p != t)
                    casTail(t, newNode);
                return true;
            }
        }
        else if (p == q)
            p = (t != (t = tail)) ? t : head;
        else
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

流程:

  1. 定位尾节点.
  2. 使用CAS将入队节点设置为尾节点的next节点,若不成功,则重试.

定位尾节点:
tail节点并非总是尾节点.每次入队必须先通过tail节点查找尾节点.

设置入队节点为尾节点:
设置入队节点为当前尾节点的next节点.

  • 刚开始head和tail节点都指向空节点.
  • 添加一个节点后,head和tail仍然指向空节点.
  • 再添加节点时,head指向空节点,tail指向尾节点.
  • 再次添加节点时,tail保持不变.
  • 再添加结点时,tail指向尾节点.

出队列

出队列是从队列中返回一个节点元素.

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

            if (item != null && p.casItem(item, null)) {
                if (p != h)
                    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节点有元素时,直接弹出head节点对应的元素,不更新head节点.
  • 当head节点没有元素时,出队操作更新head节点.

目的:减少使用CAS更新head节点的消耗,提高效率.

  • 先获取头节点的元素.
  • 再判断头节点元素是否为空.如果为空,则另外一个线程已进行出队操作.
  • 若不为空,则使用CAS方式将头节点的引用设为null.成功则直接返回头节点元素;不成功则其他线程已进行出队操作更新head节点,需重新获取头节点.

参考:

原文地址:https://www.cnblogs.com/truestoriesavici01/p/13214038.html