LinkedBlockingQueue原理分析---基于JDK8

本文详细分析了LinkedBlockingQueue的实现原理,包括其基于链表的数据结构、线程安全的双锁机制以及在并发场景下的高性能表现。重点讨论了put、take等关键操作的实现,并解释了如何通过signalNotFull()和notEmpty.signal()来协调生产者和消费者的线程同步。
摘要由CSDN通过智能技术生成

1.常用的阻塞队列 

1)ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的.

2)LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的

3)PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.

4)SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的.

其中LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue


2.LinkedBlockingQueue原理

  1. 基于链表实现,线程安全的阻塞队列。
  2. 使用锁分离方式提高并发,双锁(ReentrantLock):takeLock、putLock,允许读写并行,remove(e)和contain()、clear()需要同时获取2个锁。
  3. FIFO先进先出模式。
  4. 在大部分并发场景下,LinkedBlockingQueue的吞吐量比ArrayBlockingQueue更好,双锁,入队和出队同时进行
  5. 根据构造传入的容量大小决定有界还是无界,默认不传的话,大小Integer.Max


3.LinkedBlockingQueue的几个关键属性

static class Node<E> {
        E item;

        /**后继节点
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

    /** 队列容量,默认最大,可指定大小 */
    private final int capacity;

    /** 当前容量 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * 头节点.
     * Invariant: head.item == null
     */
    transient Node<E> head;

    /**
     * 尾节点.
     * Invariant: last.next == null
     */
    private transient Node<E> last;

    /** 定义的出队和入队分离锁,2个队列空和满的出队和入队条件 Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

构造函数:默认是队列,可指定为有界,或初始给于一个初始集合数据


public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    /**
     * 指定有界大小,同时初始化head和tail节点
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

    /**
     * 遍历集合元素,放到队列进行初始化  ---  无界队列
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     */
    public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }

4.BlockingQueue源码分析

    //入队,将元素添加到对尾等价  last.next = node; last = last.next
    private void enqueue(Node<E> node) {
        last = last.next = node;
    }

    /**
     * 出队,从头部出
     *
     * @return the node
     */
    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }


// 队列已满:false  
public boolean offer(E e) {  
    if (e == null) throw new NullPointerException();  
    final AtomicInteger count = this.count;  
    if (count.get() == capacity) // 队列容量达到最大值,添加失败  
        return false;  
    int c = -1;  
    Node<E> node = new Node<E>(e);  
    final ReentrantLock putLock = this.putLock;  
    putLock.lock(); // 获取插入锁putLock  
    try {  
        if (count.get() < capacity) { // 加锁后再次判断队列是否已满  
            enqueue(node); // 入队  
            c = count.getAndIncrement(); // 返回Inc之前的值  
            if (c + 1 < capacity) // 插入节点后队列未满  
                notFull.signal(); // 唤醒notFull上的等待线程  
        }  
    } finally {  
        putLock.unlock(); // 释放插入锁  
    }  
    if (c == 0)  
        signalNotEmpty(); // 如果offer前队列为空,则唤醒notEmpty上的等待线程  
    return c >= 0;  
}  



public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException 方法和 offer(E e)代码和功能均相似,但是如果在指定时间内未插入成功则会返回false。
比offer(E e)多的部分代码分析:
long nanos = unit.toNanos(timeout);  //将指定的时间长度转换为毫秒来进行处理  
while (count.get() == capacity) {  
    if (nanos <= 0) // 等待的剩余时间小于等于0,那么直接返回false  
        return false;  
    nanos = notFull.awaitNanos(nanos); // 最多等待时间(纳秒)  
}  


//插入节点:\n线程入队操作前会获取putLock锁,插入数据完毕后释放;
队列未满将新建Node节点,添加到队列末尾;
队列已满则阻塞线程(notFull.await())或返回false;若线程B取出数据,则会调用notFull.signal()唤醒notFull上的等待线程(线程A继续插数据)。
若入队前队列为空,则唤醒notEmpty上等待的获取数据的线程
// 一直阻塞直到插入成功  
public void put(E e) throws InterruptedException {  
    if (e == null) throw new NullPointerException();  
    // Note: convention in all put/take/etc is to preset local var  
    // holding count negative to indicate failure unless set.  
    int c = -1;  
    Node<E> node = new Node<E>(e);  
    final ReentrantLock putLock = this.putLock;  
    final AtomicInteger count = this.count;  
 // 可中断的锁获取操作(优先考虑响应中断),如果线程由于获取锁而处于Blocked状态时,线程将被中断而不再继续等待(throws InterruptedException),可避免死锁。  
    putLock.lockInterruptibly();  
    try {  
        /* 
         * Note that count is used in wait guard even though it is 
         * not protected by lock. This works because count can 
         * only decrease at this point (all other puts are shut 
         * out by lock), and we (or some other waiting put) are 
         * signalled if it ever changes from capacity. Similarly 
         * for all other uses of count in other wait guards. 
         */  
// 队列若满线程将处于等待状态。while循环可避免“伪唤醒”(线程被唤醒时队列大小依旧达到最大值)  
        while (count.get() == capacity) {  
            notFull.await(); // notFull:入队条件  
        }  
        enqueue(node); // 将node链接到队列尾部  
        c = count.getAndIncrement(); // 元素入队后队列元素总和  
        if (c + 1 < capacity) // 队列未满  
            notFull.signal(); // 唤醒其他执行入队列的线程  
    } finally {  
        putLock.unlock(); // 释放锁  
    }  
// c=0说明队列之前为空,出队列线程均处于等待状态。添加一个元素后,队列已不为空,于是唤醒等待获取元素的线程  
    if (c == 0)  
        signalNotEmpty();  
}  


获取方法
先看几个重要方法:
  1. /** 
  2.  * 唤醒等待插入数据的线程. Called only from take/poll. 
  3.  */  
  4. private void signalNotFull() {  
  5.     final ReentrantLock putLock = this.putLock;  
  6.     putLock.lock();  
  7.     try {  
  8.         notFull.signal();  
  9.     } finally {  
  10.         putLock.unlock();  
  11.     }  
  12. }  
  13. /** 
  14. * 队列头部元素出队. 
  15. * 
  16. * @return the node 
  17. */  
  18. private E dequeue() {  
  19.     // assert takeLock.isHeldByCurrentThread();  
  20.     // assert head.item == null;  
  21.     Node<E> h = head; // 临时变量h  
  22.     Node<E> first = h.next;  
  23.     h.next = h; // 形成环引用help GC  
  24.     head = first;  
  25.     E x = first.item;  
  26.     first.item = null;  
  27.     return x;  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值