LinkedBlockingQueue 源码分析 (基于Java 8)

1. LinkedBlockingQueue定义

一个基于链表实现的有固定容量的 FIFO 的阻塞队列, 队列中存在最久的元素存在于 head.next 节点上(PS: head 节点永远存在, 且是一个 dummy 节点), 存储时间最短的节点存储在tail上; 通常情况下 LinkedBlockingQueue 的吞吐量要好于 ArrayBlockingQueue.
主要特点:

  1. 基于两个lock的 queue, putLock, takeLock; 并且两个锁都有相关联的 condition 用于相应的 await; 每次进行 put/offer 或 take/poll 之后会根据queue的容量进行判断是否需要进行对应的唤醒
  2. 队列中总是存在一个 dummy 节点, 每次 poll 节点时获取的是 head.next 节点中的值
2. LinkedBlockingQueue 基本属性

queue中的数据存储在 Node 对象中, 且 Node 具有以下的特点:

  1. head.item 永远是 null, head是一个 dummy 节点, 所以进行 poll 时获取的是 head.next 的值
  2. tail.next = null
/** Linked list node class */
/**
 * Linked 的数据节点, 这里有个特点, LinkedBlockingQueue 开始构建时会创建一个dummy节点(类似于 ConcurrentLinkedQueue)
 * 而整个队列里面的头节点都是 dummy 节点
 * @param 
 */
static class Node{
    E item;

    /**
     * One of:
     * - the real successor Node
     * - this Node, meaning the successor is head.next
     * - null, meaning there is no successor (this is the last node)
     */
    /**
     * 在进行 poll 时 会将 node.next = node 来进行 help gc
     * next == null 指的是要么是队列的 tail 节点
     */
    Node next;
    Node(E x){
        item = x;
    }
}

/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;

/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();

/**
 * Head of linked list
 * Invariant: head.item == null
 * Head 节点的不变性 head.item == null <- 这是一个 dummy 节点(dummy 节点什么作用呢, 主要是方便代码, 不然的话需要处理一些 if 的判断, 加大代码的复杂度, 尤其是非阻塞的实现)
 */
transient Node head;

/**
 * Tail of linked list
 * Invariant: last.next == null
 * Tail 节点的不变性 last.next == null <- 尾节点的 next 是 null
 */
private transient Node last;

/** ReentrantLock Condition 的常见使用方式 */
/** 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();
3. LinkedBlockingQueue 构造函数

LinkedBlockingQueue的构造函数比较简单, 主要是初始化一下容量(默认 Integer.MAX_VALUE), 及 head, tail

/**
 * Creates a {@code KLinkedBlockingQueue} with the given (fixed) capacity
 *
 * @param capacity the capacity of this queue
 * @throws IllegalArgumentException if {@code capacity} is not greater
 *                                  than zero
 */
public KLinkedBlockingQueue(int capacity){
    if(capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity; // 指定 queue 的容量
    last = head = new Node(null); // 默认的在 queue 里面 创建 一个 dummy 节点
}
4. 添加元素 put方法

put 方法是将元素添加到队列尾部, queue满时进行await, 添加成功后容量还未满, 则进行 signal

/**
 * Inserts the specified element at the tail of this queue, waiting if
 * necessary for space to become available
 *
 *  将元素加入到 queue 的尾部
 * @param e
 * @throws InterruptedException
 */
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 negativeto indicate failure unless set.
    // 有趣的 变量 c 下面会有对它的讲解
    int c = -1;
    Node node = new Node(e);
    final ReentrantLock putLocK = this.putLock;
    final AtomicInteger count = this.count;  // 获取 queue 的数量 count (这是 count 只可能 减, 不能增)
    putLocK.lockInterruptibly(); // 获取 put 的lock
    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
         */
        /**
         * 若 queue 的容量满了 则进行 await,直到有人进行通知
         * 那何时进行通知呢?
         * 有两种情况进行通知,
         *      (1) 有线程进行 put/offer 成功后且 (c + 1) < capacity 时
         *      (2) 在线程进行 take/poll 成功 且 (c == capacity) (PS: 这里的 c 指的是 在进行 take/poll 之前的容量)
         */

        while(count.get() == capacity){     // 容量满了, 进行等待
            notFull.await();
        }
        enqueue(node);                        // 进行节点的入队操作
        c = count.getAndIncrement();          // 进行节点个数的增加1, 返回原来的值
        if(c + 1 < capacity){               // 说明 现在的 put 操作后 queue 还没满
            notFull.signal();               // 唤醒其他在睡的线程
        }

    }finally {
        putLock.unlock();                   // 释放锁
    }
    if(c == 0){                             // c == 0 说明 原来queue是空的, 所以这里 signalNotEmpty 一下, 唤醒正在 poll/take 等待中的线程
        signalNotEmpty();
    }
}

/**
 * Links node at end of queue
 * 节点 入队列 (PS: 这里因为有个 dummy 节点, 不需要判空 <- 现在有点感觉 dummy 节点的作用了吧)
 * @param node the node
 */
private void enqueue(Node node){
    // assert putLock.isHeldByCurrentThread()
    // assert last.next == null
    last = last.next = node;
}

/**
 * Signals a waiting take. Called only from put/offer (which do not
 * otherwise ordinarily lock takeLock.)
 */
private void signalNotEmpty(){
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    }finally {
        takeLock.unlock();
    }
}

代码的注释中基本把操作思想都说了, 有几个注意的地方

  1. 当queue满时, 会调用 notFull.await() 进行等待, 而相应的唤醒的地方有两处, 一个是 "有线程进行 put/offer 成功后且 (c + 1) < capacity 时", 另一处是 "在线程进行 take/poll 成功 且 (c == capacity) (PS: 这里的 c 指的是 在进行 take/poll 之前的容量)"
  2. 代码中的 "signalNotEmpty" 这时在原来queue的数量 c (getAndIncrement的返回值是原来的值) ==0 时对此时在调用 take/poll 方法的线程进行唤醒
5. 添加元素offer 方法

offer与put都是添加元素到queue的尾部, 只不过 put 方法在队列满时会进行阻塞, 直到成功; 而 offer 操作在容量满时直接返回 false.

/**
 * Inserts the specified element at the tail of this queue, waiting if
 * necessary up to the specified wait time for space to become available
 *
 *  支持中断和超时的 offer 节点
 *
 * @param e
 * @param timeout
 * @param unit
 * @return {@code true} if successful, or {@code false} if
 *          the specified waiting time elapses before space is available
 * @throws InterruptedException
 */
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException{
    if(e == null) throw new NullPointerException();
    long nanos = unit.toNanos(timeout);
    int c = -1;
    final ReentrantLock putLock = this.putLock;     // 获取 put lock
    final AtomicInteger count = this.count;         // 获取 queue 的容量
    putLock.lockInterruptibly();
    try {
        while(count.get() == capacity){             // queue的容量满了进行 带 timeout 的 await
            if(nanos <= 0){                           //  用光了 timeout 直接 return false
                return false;
            }
            nanos = notFull.awaitNanos(nanos);      // 直接 await (PS: 返回值 nanos <= 0 说明 等待是超时了, 正常 await 并且 被 signal nanos > 0; 具体详情会在 Condition 那一篇中详细说明)
        }
        enqueue(new Node(e));                    // 节点若队列
        c = count.getAndIncrement();                // 获取入队列之前的容量
        if(c + 1 < capacity){                     // c + 1 < capacity 说明 现在的 offer 成功后 queue 还没满
            notFull.signal();                     // 唤醒其他正在 await 的线程
        }
    }finally {
        putLock.unlock();                           // 释放锁
    }
    if(c == 0){
        signalNotEmpty();                            // c == 0 说明 原来queue是空的, 所以这里 signalNotEmpty 一下, 唤醒正在 poll/take 等待中的线程
    }
    return true;
}

offer 整个操作和 put 差不多, 唯一变化的是多了一个 notFull.awaitNanos(nanos), 这个函数的返回值若是负数, 则说明等待超时, 则直接 return false (关于 Condition.awaitNanos 方法会在后续再说)

6. 获取queue头元素 take 方法

此方法是获取 queue 中呆着时间最长的节点的值(head.next)

/**
 * 取走 queue 中呆着时间最长的节点的 item (其实就是 head.next.item 的值)
 * @return
 * @throws InterruptedException
 */
public E take() throws InterruptedException{
    E x;
    int c = -1;
    final AtomicInteger count = this.count;          // 获取 queue 的容量
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();                      // 获取 lock
    try {
        while(count.get() == 0){                      // queue 为空, 进行 await
            notEmpty.await();
        }
        x = dequeue();                                 // 将 head.next.item 的值取出, head = head.next
        c = count.getAndDecrement();                   // queue 的容量计数减一
        if(c > 1){
            notEmpty.signal();                        // c > 1 说明 进行 take 后 queue 还有值
        }
    }finally {
        takeLock.unlock();                              // 释放 lock
    }
    if(c == capacity){                                // c == capacity 说明一开始 queue 是满的, 调用 signalNotFull 进行唤醒一下 put/offer 的线程
        signalNotFull();
    }
    return x;
}

/**
 * Removes a node from head of queue
 * 节点出队列 这里有个注意点 head 永远是 dummy 节点, dequeue 的值是 head.next.item 的值
 * 在 dequeue 后 将 原  head 的后继节点设置为 head(成为了 dummy 节点)
 * @return the node
 */
private E dequeue(){
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    Node h = head;       // 这里的 head 是一个 dummy 节点
    Node first = h.next; // 获取真正的节点
    h.next = h;             // help GC
    head = first;           // 重行赋值 head
    E x = first.item;       // 获取 dequeue 的值
    first.item = null;      // 将 item 置 空
    return x;
}

/** Signal a waiting put. Called only from take/poll */
private void signalNotFull(){
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    }finally {
        putLock.unlock();
    }
}

操作过程: 将 head.next 的值取出, 将 head.next 设置为新的head; 操作的步骤比较少, 只有两处 condition 的唤醒需要注意一下:

  1. 当 take 结束时, 判断 queue 是否还有元素 (c > 1) 来进行 notEmpty.signal()
  2. 当 take 结束时, 判断原先的容量是否已经满 (c == capacity) 来决定是否需要调用 signalNotFull 进行唤醒此刻还在等待 put/offer 的线程
7. 获取queue头元素 poll 方法

poll 与 take 都是获取头节点的元素, 唯一的区别是 take在queue为空时进行await, poll 则直接返回

/**
 * 带 timeout 的poll 操作, 获取 head.next.item 的值
 * @param timeout
 * @param unit
 * @return
 * @throws InterruptedException
 */
public E poll(long timeout, TimeUnit unit) throws InterruptedException{
    E x = null;
    int c = -1;
    long nanos = unit.toNanos(timeout);             //  计算超时时间
    final AtomicInteger count = this.count;       // 获取 queue 的容量
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();                   // 获取 lock
    try{
        while(count.get() == 0){                   // queue 为空, 进行 await
            if(nanos <= 0){                        // timeout 用光了, 直接 return null
                return null;
            }
            nanos = notEmpty.awaitNanos(nanos);   // 调用 condition 进行 await, 在 timeout之内进行 signal -> nanos> 0
        }
        x = dequeue();                             // 节点出queue
        c = count.getAndDecrement();               // 计算器减一
        if(c > 1){                                 // c > 1 说明 poll 后 容器内还有元素, 进行 换新 await 的线程
            notEmpty.signal();
        }
    }finally {
        takeLock.unlock();                         // 释放锁
    }
    if(c == capacity){                           // c == capacity 说明一开始 queue 是满的, 调用 signalNotFull 进行唤醒一下 put/offer 的线程
        signalNotFull();
    }
    return x;
}
8. 删除queue元素 remove 方法
/**
 * Removes a single instance of the specified element from this queue,
 * if it is present. More formally, removes an element {@code e} such
 * that {@code o.equals(e)}, if this queue contains one or more such
 * elements
 * Returns {@code true} if this queue contained the specified element
 * (or equivalently, if this queue changed as a result of the call)
 *
 * 删除 queue 中的节点
 *
 * @param o element to be removed from this queue, if present
 * @return {@code true} if this queue changed as a result of the call
 */
public boolean remove(Object o){
    if(o == null) return false;
    fullyLock();                                         // 获取所有锁

    try {
        for(Node trail = head, p = trail.next;     // 进行变量的初始化 trail是 p 的前继节点
                p != null;
                trail = p, p = p.next){
            if(o.equals(p.item)){
                unlink(p, trail);                      // 调用 unlink 进行删除
                return true;
            }
        }
        return false;
    }finally {
        fullyUnlock();                                  // 释放所有锁
    }
}

/** Unlinks interior Node p with predecessor trail */
/**
 * 直接将这个方法看做是 将 节点 p 从 queue 中进行删除
 * @param p
 * @param trail
 */
void unlink(Node p, Node trail){
    // assert isFullLocked();
    // p.next is not changed, to allow iterators that are
    // traversing p to maintain their weak-consistency guarantee
    p.item = null;                      // 删除 p.item
    trail.next = p.next;                // 删除节点 p
    if(last == p){                      // 若节点p 是last, 则将p的前继节点trail置为 last
        last = trail;
    }
    if(count.getAndDecrement() == capacity){    // count.getAndDecrement() == capacity 说明 queue 在删除节点之前是满的, 所以唤醒一下在 put/offer 的线程
        notFull.signal();
    }
}

remove的代码比较少, 有两次需要注意:

  1. trail 是 节点 p 的前继节点
  2. 删除结束后会判断之前的容量是否是满的来决定 调用notFull.signal() 进行线程唤醒操作
9. 总结

LinkedBlockingQueue 是一个基于链表实现的阻塞queue, 它的性能好于 ArrayBlockingQueue, 但是差于 ConcurrentLinkeQueue; 并且它非常适于生产者消费者的环境中, 比如 Executors.newFixedThreadPool() 就是基于这个队列的。

你可能感兴趣的:(LinkedBlockingQueue 源码分析 (基于Java 8))