Android 并发编程ConcurrentLinkedQueue

实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现。1.ConcurrentLinkedQueue的结构首先看一下源码,从中你可以发现,它就是个队列的数据结构。private static class Node<E> { volatile E item; vola...
摘要由CSDN通过智能技术生成

在并发编程中有时候需要使用线程安全的队列。要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现。ConcurrentLinkedQueue是使用非阻塞的方式来实现线程安全队列的。

 

1.ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,采用先进先出的规则对节点进行排序。添加一个元素的时候,它会添加到队列的尾部,获取一个元素时,它会返回队列头部的元素。

ConcurrentLinkedQueue的节点都是Node类型的,源码如下:

private static class Node<E> {

        volatile E item;

        volatile Node<E> next;

        // ........

}

//头节点

private transient volatile Node<E> head;

//尾节点

private transient volatile Node<E> tail;

Node节点主要包含了两个域:一个是数据域item,另一个是next指针,用于指向下一个节点,从而构成链式队列,并且都是用volatile进行修饰的,以保证内存可见性。

 

ConcurrentLinkedQueue类有两个构造方法:

①默认构造方法,head节点存储的元素为空,tail节点等于head节点

public ConcurrentLinkedQueue() {

    head = tail = new Node<E>(null);

}

②根据其他集合来创建队列

public ConcurrentLinkedQueue(Collection<? extends E> c) {

    Node<E> h = null, t = null;

    // 遍历节点

    for (E e : c) {

        //若节点为null,则直接抛出空指针异常

        checkNotNull(e);

        Node<E> newNode = new Node<E>(e);

        if (h == null)

            h = t = newNode;

        else {

            t.lazySetNext(newNode);

            t = newNode;

        }

    }

    if (h == null)

        h = t = new Node<E>(null);

    head = h;

    tail = t;

}

默认情况下head节点存储的元素为空,tail节点等于head节点。

head = tail = new Node<E>(null);

 

2.入队操作offer

入队列就是将入队节点添加到队列的尾部。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16上图所示的元素添加过程如下:

①添加元素1:队列更新head节点的next节点为元素1节点。又因为tail节点默认情况下等于head节点,所以它们的next节点都指向元素1节点。

②添加元素2:队列首先设置元素1节点的next节点为元素2节点,然后更新tail节点指向元素2节点,tail的next指向null。

③添加元素3:设置tail节点的next节点为元素3节点。

④添加元素4:设置元素3的next节点为元素4节点,然后将tail节点指向元素4节点,然后将tail的next指向null。

入队操作主要做两件事情,第一是将入队节点设置成当前队列尾节点的下一个节点。第二是更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点;如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点不总是尾节点,理解这一点很重要。

这是从单线程入队的角度来理解入队过程,但是多个线程同时进行入队,情况就变得更加复杂,因为可能会出现其他线程插队的情况。如果有一个线程正在入队,那么它必须先获取尾节点,然后设置尾节点的下一个节点为入队节点,但这时可能有另外一个线程插队了,那么队列的尾节点就会发生变化,这时当前线程要暂停入队操作,然后重新获取尾节点。

来看看ConcurrentLinkedQueue的add入队方法:

public boolean add(E e) {

    return offer(e);

}

public boolean offer(E e) {

    // 创建入队节点,如果e为null,则直接抛出空指针异常

    final Node<E> newNode = newNode( Objects.requireNonNull(e));

    // 循环CAS直到入队成功:不断重试(for只有初始化条件,没有判断条件),直到将node加入队列

    for (Node<E> t = tail, p = t;;) {

        Node<E> q = p.next; // q一直指向p的下一个

       //判断p是不是尾节点,q为null表示p是最后一个元素,尝试加入队列

        if (q == null) {

            //设置p节点的下一个节点为新节点,设置成功则casNext返回true;否则返回false,说明有其他线程更新过尾节点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值