JUC知识点总结(七)ConcurrentLinkedQueue知识点总结

13. ConcurrentLinkedQueue (循环CAS)

应用场景:

按照适用的并发强度从低到高排列如下:

  • LinkedList/ArrayList 非线程安全,不能用于并发场景(List的方法支持栈和队列的操作,因此可以用List封装成stack和queue);
  • Collections.synchronizedList 使用wrapper class封装,每个方法都用synchronized(mutex:Object)做了同步
  • LinkedBlockingQueue 采用了锁分离的设计,避免了读/写操作冲突,且自动负载均衡,可以有界。BlockingQueue在生产-消费模式下首选【Iterator安全,不保证数据一致性】
  • ConcurrentLinkedQueue 适用于高并发读写操作,理论上有最高的吞吐量,无界,不保证数据访问实时一致性,Iterator不抛出并发修改异常,采用CAS机制实现无锁访问。

综上:

  • 在并发的场景下,如果并发强度较小,性能要求不苛刻,且锁可控的场景下,可使用Collections.synchronizedList,既保证了数据一致又保证了线程安全,性能够用;
  • 在大部分高并发场景下,建议使用 LinkedBlockingQueue ,性能与 ConcurrentLinkedQueue 接近,且能保证数据一致性;
  • ConcurrentLinkedQueue 适用于超高并发的场景,但是需要针对数据不一致采取一些措施。
源码分析
offer(E e)
public boolean offer(E e) {
    checkNotNull(e);
    //创建入队节点
    final Node<E> newNode = new Node<E>(e);
    //t为tail节点,p为尾节点,默认相等,采用失败即重试的方式,直到入队成功
    for (Node<E> t = tail, p = t; ; ) {
        //获得p的下一个节点
        Node<E> q = p.next;
        // 如果下一个节点是null,也就是p节点就是尾节点
        if (q == null) {
            //将入队节点newNode设置为当前队列尾节点p的next节点
            if (p.casNext(null, newNode)) {
                //判断tail节点是不是尾节点,也可以理解为如果插入结点后tail节点和p节点距离达到两个结点
                if (p != t)
                    //如果tail不是尾节点则将入队节点设置为tail。
                    // 如果失败了,那么说明有其他线程已经把tail移动过 
                    casTail(t, newNode);
                return true;
            }
        }
        // 如果p节点等于p的next节点,则说明p节点和q节点都为空,表示队列刚初始化,所以返回                            head节点
        else if (p == q)
            p = (t != (t = tail)) ? t : head;
        else
            //p有next节点,表示p的next节点是尾节点,则需要重新更新p后将它指向next节点
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

即定位出尾节点=>CAS入队=>重新定位tail节点。

poll( )
public E poll() {
    // 设置起始点  
    restartFromHead:
    for (; ; ) {
        //p表示head结点,需要出队的节点
        for (Node<E> h = head, p = h, q; ; ) {
            //获取p节点的元素
            E item = p.item;
            //如果p节点的元素不为空,使用CAS设置p节点引用的元素为null
            if (item != null && p.casItem(item, null)) {

                if (p != h) // hop two nodes at a time
                    //如果p节点不是head节点则更新head节点,也可以理解为删除该结点后检查head是否与头结点相差两个结点,如果是则更新head节点
                    updateHead(h, ((q = p.next) != null) ? q : p);
                return item;
            }
            //如果p节点的下一个节点为null,则说明这个队列为空,更新head结点
            else if ((q = p.next) == null) {
                updateHead(h, p);
                return null;
            }
            //结点出队失败,重新跳到restartFromHead来进行出队
            else if (p == q)
                continue restartFromHead;
            else
                p = q;
        }
    }
}

即获取head节点的元素 => 判断head节点元素是否为空=>如果为空,表示另外一个线程已经进行了一次出队操作将该节点的元素取走=>如果不为空,则使用CAS的方式将head节点的引用设置成null=>如果CAS成功,则直接返回head节点的元素=>如果CAS不成功,表示另外一个线程已经进行了一次出队操作更新了head节点,导致元素发生了变化,需要重新获取head节点=>如果p节点的下一个节点为null,则说明这个队列为空(此时队列没有元素,只有一个伪结点p),则更新head节点。

特点
  • 访问操作采用了无锁设计
  • Iterator的弱一致性,即不保证Iteartor访问数据的实时一致性(与current组的成员与COW成员类似)
  • 并发offer/poll
注意事项

size操作需要遍历整个队列,且如果此时queue正在被修改,size可能返回不准确的数值(仍然是无法保证数据一致性),这是一个非常耗时的操作,判断队列是否为空建议使用isEmpty()。如果需要保证数据一致性,频繁获取集合对象的size,最好不使用concurrent族的成员。

批量操作(bulk operations like addAll,removeAll,equals)无法保证原子性,因为不保证实时性,且没有使用独占锁的设计。例如,在执行addAll的同时,有另外一个线程通过Iterator在遍历,则遍历的线程可能只看到一部分新增的数据。

ConcurrentLinkedQueue 没有实现BlockingQueue接口。当队列为空时,take方法返回null,此时consumer会需要处理这个情况,consumer会循环调用take来保证及时获取数据,此为busy waiting,会持续消耗CPU资源。

与 LinkedBlockingQueue 的对比
  • LinkedBlockingQueue 采用了锁分离的设计,put、get锁分离,保证两种操作的并发;
  • 当队列为空/满时,某种操作会被挂起;
  • 两者的Iterator都不不保证数据一致性,Iterator遍历的是Iterator创建时已存在的节点,创建后的修改不保证能反应出来。
  • LinkedBlockingQueue 的size是在内部用一个AtomicInteger保存,执行size操作直接获取此原子量的当前值,时间复杂度O(1)。
    ConcurrentLinkedQueue 的size操作需要遍历(traverse the queue),因此比较耗时,时间复杂度至少为O(n),建议使用isEmpty()。

下一篇
JUC知识点总结(八)CopyOnWrite机制及其在JAVA中的实现

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java JUCJava Util Concurrent)是Java平台的一个并发编程库,提供了一些并发编程的工具和框架。以下是Java JUC的一些重要知识点: 1. Lock接口和ReentrantLock类:提供了一种比Java中的synchronized关键字更灵活、可定制化的同步机制。 2. Condition接口:可以和Lock接口一起使用,提供了一种等待通知机制,可以让线程在等待某个条件成立时挂起,直到被其他线程唤醒。 3. Semaphore类:提供了一种信号量机制,可以限制某些资源的并发访问量,保证程序的稳定性。 4. CountDownLatch类:提供了一种倒计时锁机制,可以让某个线程在其他线程都完成后再执行。 5. CyclicBarrier类:提供了一种栅栏机制,可以让多个线程在某个点上进行同步,等待所有线程都到达后再同时执行。 6. Executor框架:提供了一种线程池机制,可以更好地管理线程,提高程序的性能和稳定性。 7. CompletableFuture类:提供了一种异步编程机制,可以让程序在等待某些操作的同时继续执行其他操作,提高程序的并发性能。 这些都是Java JUC的重要知识点,掌握它们可以帮助开发者更好地编写高并发、高性能的程序。 ### 回答2: Java JUCJava Util Concurrency)是Java并发编程的工具类库,提供了一些多线程编程的辅助工具和数据结构,主要包括锁、原子变量、并发容器、线程池等。 首先,Java JUC提供了多种类型的锁,如ReentrantLock、ReadWriteLock等。这些锁可以用来控制对共享资源的访问,保证线程的安全性。通过使用锁,可以实现线程的互斥访问和公平竞争访问,防止资源的并发访问导致的数据不一致的问题。 另外,Java JUC还提供了一些原子变量,比如AtomicInteger、AtomicLong等。原子变量是线程安全的,可以保证对其操作的原子性。通过使用原子变量,可以避免多线程环境下对共享变量的竞争导致的数据错乱问题。 并发容器也是Java JUC的重要组成部分,如ConcurrentHashMap、ConcurrentLinkedQueue等。这些并发容器是线程安全的,可以在多线程环境下安全地处理数据。通过使用并发容器,可以提高多线程程序的性能和并发访问的效率。 最后,Java JUC还提供了线程池的支持,通过线程池可以实现线程的复用、统一管理和调度。线程池可以减少线程的创建和销毁的开销,并且可以控制并发线程的数量,避免因为线程数过多导致系统资源耗尽的问题。 总之,Java JUC知识点涵盖了锁、原子变量、并发容器和线程池等多个方面,可以帮助程序员更好地进行多线程编程,提高程序的性能和并发访问的效率。 ### 回答3: Java JUCjava.util.concurrent)是Java中用于处理多线程并发编程的工具包。它提供了一套强大的并发编程工具和类,帮助开发者更加方便地编写高效、稳定的多线程程序。 Java JUC包含了以下几个重要的知识点: 1. 锁机制:Java JUC提供了多种类型的锁机制,包括ReentrantLock、StampedLock等,用于实现线程同步和互斥访问共享资源。通过使用锁机制,可以确保多个线程之间的数据一致性和线程安全性。 2. 阻塞队列:Java JUC提供了多种类型的阻塞队列,如ArrayBlockingQueueLinkedBlockingQueue等。阻塞队列是一种特殊的队列,当队列为空或者已满时,插入和删除操作会被阻塞,直到满足条件后再继续执行。 3. 线程池:Java JUC中的线程池机制可以重用线程,减少线程的创建和销毁开销,提高系统的性能和资源利用率。通过ThreadPoolExecutor类,可以方便地创建和管理线程池,并根据实际需求调整线程池的大小和线程池中线程的执行方式。 4. 原子操作:Java JUC提供了一系列原子类,如AtomicInteger、AtomicLong等,用于支持对共享变量进行原子操作,以避免线程竞争和数据不一致的问题。原子类提供了一系列原子性的方法,保证了多线程环境下的安全访问。 5. 并发容器:Java JUC提供了一些线程安全的并发容器,如ConcurrentHashMap、CopyOnWriteArrayList等,用于在多线程环境下安全地处理数据结构。这些并发容器支持高并发读写操作,提供更好的性能和可伸缩性。 总之,Java JUC提供了一组强大的并发编程工具和类,能够帮助开发者更好地处理多线程编程中的并发性和线程安全性问题。通过熟练掌握和应用这些知识点,可以编写出高效、稳定的多线程程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值