• LinkedBlockingQueue源码分析


    横向比较

       

         LinkedBlockingQueue对应的是LinkedLis和ConcurrentLinkedQueue,LinkedList是一个线程不安全的双向链表结构的队列,ConcurrentLinkedQueue是一个线程安全的单链表结构的队列,ConcurrentLinkedQueue解决了LinkedList线程不安全的问题,而且是通过写时next使用cas和volatile来实现,是一个无锁化操作,极大保证了读写的性能,但由此带来的缺点是读可能数据不一致,而且ConcurrentLinkedQueue是一个无界队列,操作不当的话可能会导致内存溢出。LinkedBlockingQueue主要是解决了ConcurrentLinkedQueue的两个问题:队列的无界和读数据不一致问题,但肯定在性能上就比ConcurrentLinkedQueue差一些。

    put("张三")方法

    1. public void put(E e) throws InterruptedException {
    2. if (e == null) throw new NullPointerException();
    3. int c = -1;
    4. Node<E> node = new Node<E>(e);
    5. 拿到putLock锁,队列现在元素数量count,然后上锁
    6. final ReentrantLock putLock = this.putLock;
    7. final AtomicInteger count = this.count;
    8. putLock.lockInterruptibly();
    9. try {
    10. 当队列现在元素数量等于最大容量时,使用putLock锁的condition放入await的等待队列中
    11. while (count.get() == capacity) {
    12. notFull.await();
    13. }
    14. 直接把新的node挂到last.next上并成为新的last的节点
    15. enqueue(node);
    16. 元素数量+1
    17. c = count.getAndIncrement();
    18. 元素数量+1后小于最大容量时,使用putLock锁的condition唤醒await等待队列,
    19. if (c + 1 < capacity)
    20. notFull.signal();
    21. } finally {
    22. putLock.unlock();
    23. }
    24. 当放入当前元素之前是空的,说明之前队列中没有元素,此时可能take操作有线程在takeLock的condition的await等待队列中,所以需要尝试signal takeLock锁的condition中await等待队列的线程
    25. if (c == 0)
    26. signalNotEmpty();
    27. }

    总结

    1. 在put元素时,首先拿到的putLock重入锁,而且记录元素个数的count也是AtomicInteger,保证了count累加的线程安全性

    2. LinkedBlockingQueue的node节点、head、tail,next等都没有使用volatile修饰,而且挂新节点也没有ConcurrentLinkedQueue那么复杂的自旋和判断,所以这就是锁带来的一个好处(或者说是锁粒度带来的好处,粒度越小可能存在的线程安全问题越多),代码复杂度会大大降低,各种多线程下写失败的情况就不会出现,

    3. 当队列现在元素数量等于最大容量时,使用putLock锁的condition放入await的等待队列中,并会释放putLock,这样就实现了达到容量就阻塞的效果

    4. 元素数量+1后小于最大容量时,此时说明该线程的元素放进队列后也不会达到最大容量,所以使用putLock锁的condition尝试唤醒await等待队列,这么判断的好处是减少signal无效执行的次数和多一个能唤醒putLock锁的condition的awart等待队列的方式,因为大于等于最大容量执行signal方法后,await等待队列中的线程只是挪到了AQS的等待队列,该线程即使获取到锁又会立即进入putLock锁的condition的awart等待队列,因为此时元素数量已经大于最大容量了

    5. 当放入当前元素之前是空的,说明之前队列中没有元素,此时可能take操作有线程在takeLock的condition的await等待队列中,所以需要尝试signal takeLock锁的condition中await等待队列的线程

    6. put和take实际上使用的是两把锁,写和读是互不干扰,这跟ReentrantReadWriteLock就不一样了,ReentrantReadWriteLock是写跟写互斥、写跟读互斥、读跟读不互斥。而LinkedBlockingQueue是写跟写互斥、读跟读互斥、写跟读不互斥。

    take()方法

    1. 跟put方法差不多,put方法是在tail挂元素,take是在head出元素,put方法使用了putLock,take方法使用了takeLock

    2. put方法在元素达到最大容量时会使用putLock锁的condition放入await的等待队列,take方法在队列为空时会使用takeLock锁的condition放入await的等待队列

    3. put方法在元素数量+1后小于最大容量会尝试唤醒putLock锁的condition放入await的等待线程,take方法在元素数量大于1时会尝试唤醒takeLock锁的condition放入await的等待线程

    4. put方法在放入当前元素之前是空的时会尝试signal takeLock锁的condition中await等待队列的线程,take方法在拿出当初当前元素之前时满的时会尝试signal putLock锁的condition中await等待队列的线程

  • 相关阅读:
    Attention机制学习记录(四)之Transformer
    c++加速方法大全
    系分 - 法律法规与标准化
    C#: 未能加载文件或程序集“xxx“
    【Three.js】知识梳理二十二:相机视角的平滑过渡与点击模型视角切换
    【Linux】缓冲区+磁盘+动静态库
    [springMVC学习]7、数据验证及其格式转换(debug源码解析)
    基于Transformer实现英语-->西班牙语的翻译任务
    【java毕业设计】基于java+swing+Eclipse的俄罗斯方块游戏GUI设计与实现(毕业论文+程序源码)——俄罗斯方块游戏
    第09讲:SkyWalking Agent 启动流程剖析,领略微内核架构之美
  • 原文地址:https://blog.csdn.net/shidebin/article/details/126815258