Java并发容器之非阻塞队列ConcurrentLinkedQueue

参考资料:http://blog.csdn.net/chenchaofuck1/article/details/51660521

    实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,阻塞队列就是通过使用加锁的阻塞算法实现的;另一种非阻塞的实现方式则可以使用循环CAS(比较并交换)的方式来实现。

    ConcurrentLinkedQueue是一个基于链表实现的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。默认情况下head节点存储的元素为空,tair节点等于head节点。

   一:入队

 

 

  • 入队主要做两件事情,

  第一是将入队节点设置成当前队列的最后一个节点。

  第二是更新tail节点,如果原来的tail节点的next节点不为空,则将tail更新为刚入队的节点(即队尾结点),如果原来的tail节点(插入前的tail)的next节点为空,则将入队节点设置成tail的next节点(而tial不移动,成为倒数第二个节点),所以tail节点不总是尾节点!

复制代码

public boolean offer(E e) {
            if (e == null) throw new NullPointerException();
            //入队前,创建一个入队节点
            Node</e><e> n = new Node</e><e>(e);
            retry:

            //死循环,入队不成功反复入队。

            for (;;) {

                //创建一个指向tail节点的引用

                Node</e><e> t = tail;

                //p用来表示队列的尾节点,默认情况下等于tail节点。

                Node</e><e> p = t;

                //获得p节点的下一个节点。

                   Node</e><e> next = succ(p);

         //next节点不为空,说明p不是尾节点,需要更新p后在将它指向next节点

                    if (next != null) {

                       //循环了两次及其以上,并且当前节点还是不等于尾节点

                        if (hops > HOPS && t != tail)

                            continue retry;
                             p = next;

                    }
                    //如果p是尾节点,则设置p节点的next节点为入队节点。
                    else if (p.casNext(null, n)) {
  //如果tail节点有大于等于1个next节点,则将入队节点设置成tair节点,更新失败了也没关系,因为失败了表示有其他线程成功更新了tair节点。

                 if (hops >= HOPS)

                       casTail(t, n); // 更新tail节点,允许失败

                  return true;

                    }

                   // p有next节点,表示p的next节点是尾节点,则重新设置p节点

                    else {
                        p = succ(p);
                    }

                }

            }
        }

复制代码

 

    二:出队

    不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head节点里的元素,而不会更新head节点。只有当head节点里没有元素时,则弹出head的next结点并更新head结点为原来head的next结点的next结点。

复制代码

public E poll() {
               Node</e><e> h = head;
           // p表示头节点,需要出队的节点
               Node</e><e> p = h;
 
               for (int hops = 0;; hops++) {
                    // 获取p节点的元素
                    E item = p.getItem();
                    // 如果p节点的元素不为空,使用CAS设置p节点引用的元素为null,如果成功则返回p节点的元素。
                    if (item != null && p.casItem(item, null)) {
                         if (hops >= HOPS) {
                              //将p节点下一个节点设置成head节点
                              Node</e><e> q = p.getNext();
                              updateHead(h, (q != null) ? q : p);
                         }
                         return item;
                    }
                    // 如果头节点的元素为空或头节点发生了变化,这说明头节点已经被另外一个线程修改了。那么获取p节点的下一个节点
                Node</e><e> next = succ(p);

                    // 如果p的下一个节点也为空,说明这个队列已经空了
                    if (next == null) {
                  // 更新头节点。
                         updateHead(h, p);
                      break;
            }

                    // 如果下一个元素不为空,则将头节点的下一个节点设置成头节点

                    p = next;
               }
               return null;
         }

复制代码

 

    三:非阻塞却线程安全的原因

    观察入队和出队的源码可以发现,无论入队还是出队,都是在死循环中进行的,也就是说,当一个线程调用了入队、出队操作时,会尝试获取链表的tail、head结点进行插入和删除操作,而插入和删除是通过CAS操作实现的,而CAS具有原子性。故此,如果有其他任何一个线程成功执行了插入、删除都会改变tail/head结点,那么当前线程的插入和删除操作就会失败,则通过循环再次定位tail、head结点位置进行插入、删除,直到成功为止。也就是说,ConcurrentLinkedQueue的线程安全是通过其插入、删除时采取CAS操作来保证的。不会出现同一个tail结点的next指针被多个同时插入的结点所抢夺的情况出现。

https://www.cnblogs.com/ygj0930/p/6544543.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值