zoukankan      html  css  js  c++  java
  • 一行一行读Java源码——LinkedBlockingQueue

    1、LinkedBlockingQueue概述

      Linked:链表+Blocking:阻塞+Queue:队列

      Queue:首先想到的是FIFO

      Linked:,Queue:其结构本质上是线性表,可以有链表和顺序表实现

      LinkedBlockingQueue就是链表实现

      ArrayBlockingQueue就是顺序表实现,因为Queue只在尾部操作,所以操作顺序表和链表的时间复杂度是一样的,但顺序表的实现占用更小的空间,因为不需要指针域,但是空间必须是连续的。而链表不需要连续的空间,但需要next来指向下一个结点

      

      Blocking:阻塞,LinkedBlockingQueue是线程安全的,当队列满以后,所有的入队操作都会被阻塞,当队列空的时候,所有的出队都将被阻塞。队列初始化时我们可以初始化长度Capacity.如果没有指定长度,默认是Integer.Max_Value.显然Capacity是一个不可改变的值

      

    2、LinkedBlockingQueue源码详解

      如果要看懂LinkedBlockingQueue的源码必须连接Wai/Notify以及AbstractQueuedSychronizer(AQS),个人认为并发编程必须要掌握的三个知识。等待通知机制、CAS、AQS

      2.1 Head和Last分别表示队列的头尾,可快速定位take和put操作位置。注意头结点head和首结点First的区别

      

      2.2 Count表示当前队列中元素的个数,其使用并发包下面的AtomicInteger类来实现原子操作,该类的核心操作还是CAS, AtomicInteger对于队列的线程安全有着至关重要的作用,因为接下来要看take和put是两个相对独立的锁

      2.3、有兴趣的话可以看看ArrayBlockingQueue,其中只使用了一个锁来保证线程安全,所以它的Count没有使用AtomicInteger,而是一个int类型

      

      2.3、锁与条件

        TakeLock和PutLock分别定义了take和put操作的锁

        take操作的条件是notEmpty,所以在执行take操作的时候,要先判断当前队列是否还有元素可以take.如果没有那么就要执行notEmpty.await()方法让take线程阻塞

        put操作的条件是notFull,所以在执行put操作的时候,会先判断当前队列是否还有空间可以put操作。如果没有空间那么put制作执行notFull.await()方法

         成功take以后,会判断take之前队列是不是满的,如果是可能会有put线程被阻塞了,所以会调用singnalNotFull()方法去唤醒那些阻塞的put线程

            成功put以后会判断在put之前队列是不是空的,如果是,说明可能会有take线程是阻塞的,所以会调用SignalNotEmpty()去唤醒那些take线程

           前两部设计的相当好,先判断Empty或Full,然后再去调用唤醒方法。避免无谓的唤醒操作

        

       2.4:LinkedBlockingQueue构造方法

      

      put操作

      1、在尾部插入一个指定的元素e,如果没有空间,线程将会等待

        2、e不允许为空,该队列不存储null元素,否则抛出NullPointException()异常

      3、局部变量c初始值是-1,其存储当前队列的元素个数,准确的说是put之前的操作个数,因为c=count.getAndIncrent(),而getAndIncrentment()返回的是previous()值(c的值很重要,不然无法理解唤醒的操作) 

      4、putLock.Interruptily()获取锁

      5、如果count.get()==Capacity.说明队列已经没有剩余空间了,那么条件为NotFull的操作,put操作线程要执行语句notFull.await()进入等待。否则正常入队

      6、正常入队后,count+1,c获取的是入队前的值(这点需要注意)

      7、c+1 < capacity,表示当前队列的个数小于capacity,那么就可以唤醒一下那些条件为not Empty的put操作线程(当日此时不一定有等待线程)

      8、如果c==0,即put之前队列是空的,那么就有可能take操作线程在等待,所以执行signalNotEmpty(),该方法先获取take锁,然后唤醒等待take线程来take

      

      1、|take和put原理上是相同的,take是从first节点开始出队,注意区分head;如果队列中没有节点,那么take线程就需要等待

      2、局部变量c初始值为-1,其存储当前队列的元素个数,准确地说是take操作之前的元素个数

      3、takeLock.lockInterruptibly() 获取take lock

      4、count.get() == 0,如果当前队列中没有元素,那么条件为not Empty 的take操作线程将要等待;否则正常出队

      5、c > 1 表示take以前队列中至少是有2个元素,那么可以唤醒其它在等待的take线程,操作为notEmpty.signal()

      6、c == capacity 表示take操作前队列是满的,那么就有可能有put线程在等待着,因此执行signalNotFull(),该方法首先获取put锁,然后唤醒那些可能在等待的put线程

      

      1、重载的两个offer方法本质上也是put操作,但在操作上略有不同。

      2、一个offer方法提供了线程等待时间,其先进入条件的等待队列等待

      3、另一个offer方法是能入队就入队,不能就返回false,不等了

      4、这两种offer方法可根据实际需要来适当选择

    poll与peek

      1、两个poll方法也是take的改版,一个是超时等待,一个干脆就不等了,有就取,没有就算了。两种方法可在实际应用中按需选用

      2、peek方法和take方法不同的是没有出队,只是"看看"首元素first.item

    总结

      1、LinkedBlockingQueue体现了生产者/消费者模型,借助wait/notify机制,可实现take、put操作线程的等待与唤醒

      2、AtomicInteger类型的count(队列中当前元素个数)以及双锁机制(take和put锁)共同使得LinkedBlockingQueue是线程安全的。实现方式值得学习和体会

      3、

  • 相关阅读:
    topcoder srm 681 div1
    topcoder srm 683 div1
    topcoder srm 684 div1
    topcoder srm 715 div1
    topcoder srm 685 div1
    topcoder srm 687 div1
    topcoder srm 688 div1
    topcoder srm 689 div1
    topcoder srm 686 div1
    topcoder srm 690 div1 -3
  • 原文地址:https://www.cnblogs.com/hanxue112253/p/10096190.html
Copyright © 2011-2022 走看看