JDK源码之LinkedBlockingQueue(源码注释)

LinkedBlockingQueue,基于链表实现的先进先出队列,与ArrayBlockingQueue相比,LinkedBlockingQueue的重入锁被分成两部分,分别对应存值和取值(被称作双锁队列算法),因此可以同时进行读操作和写操作,所以理论上吞吐量超过ArrayBlockingQueue。

主要成员变量

    /**
     * 链表节点类
     */
    static class Node<E> {
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }

    /** 最大容量 */
    private final int capacity;

    /** 当前队列长度,采用AtomicInteger原子增减 */
    private final AtomicInteger count = new AtomicInteger();

    /** 头结点 */
    transient Node<E> head;

    /** 尾结点 */
    private transient Node<E> last;

    /** 取值锁(take/poll等) */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** 表示“队列非空”的对象监视器 */
    private final Condition notEmpty = takeLock.newCondition();

    /** 存值锁(put/offer等) */
    private final ReentrantLock putLock = new ReentrantLock();

    /** 表示“队列非满”的对象监视器 */
    private final Condition notFull = putLock.newCondition();

主要方法
①signalNotEmpty()和signalNotFull()
signalNotEmpty() 负责唤醒对应的出队操作
signalNotFull() 负责唤醒对应的入队操作

	private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }
    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }

思考:为什么要封装signal方法?
以put方法为例(具体源码看方法②),put方法是队列存值操作,需要使用putLock进行加锁,但是存值方法执行完后,队列内有值存在,需要takeLock锁对notEmpty进行唤醒,为了方便调用对signal方法进行封装。

②put(E e)

public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
        	// 判断队列是否已满,是则阻塞否则进行入队操作
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            // count原子操作自增1,相当于count++
            c = count.getAndIncrement();
            // 如果c+1<容量,表示可以通知非满状态对象监视器,阻塞的入队操作可以进行入队
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        // c == 0说明count是从0变化到1,表示队列非空,需要通知等待队列非空状态的线程(例如阻塞的出队操作等)
        if (c == 0)
            signalNotEmpty();
    }

插曲!!!
上面的源码中最后两行if判断c==0的地方,一开始看的时候发现有问题,问题是count代表队列的元素数量,当队列为空时,count=0,进行自增后赋值给c,此时c=1,必不可能存在c=0的情况,仔细研究发现问题的关键在于自增方法,源码中用的是c = count.getAndIncrement();是先获取count赋值给c后,count++,还有一种是c = count.incrementAndGet();是先count++后赋值给c,这样问题就解决了,当队列为空时count = 0,进行put入队操作,首先把count = 0赋值给c然后count++,此时c = 0,队列由原来的空变为有一个元素,此时唤醒等待队列非空状态的线程。
代码示例:

    public static void main(String[] args) {
        int c1 = -1;
        AtomicInteger count1 = new AtomicInteger(0);
        c1 = count1.getAndIncrement();
        System.out.println(c1);

        int c2 = -1;
        AtomicInteger count2 = new AtomicInteger(0);
        c2 = count2.incrementAndGet();
        System.out.println(c2);

    }

运行结果
JDK源码之LinkedBlockingQueue(源码注释)_第1张图片

③offer(E e)
尝试入队一次,如果失败则返回false,offer和put方法的区别就是offer判断count是否达到了最大容量,即判断队列是否已满,如果队列已满则直接返回false而put方法会阻塞入队操作。

    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();
        try {
            if (count.get() < capacity) {
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

④offer(E, long, TimeUnit)
此入队方法会阻塞入队操作指定时间,并在这个时间段内不断尝试入队操作,超时后返回false。此方法跟put方法类似,但是put方法会一直阻塞线程并尝试入队,直到成功。

    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        // 将timeout转换成纳秒数
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;
    }

⑤remove(Object) 删除指定对象

    public boolean remove(Object o) {
        if (o == null) return false;
        fullyLock();
        try {
            for (Node<E> trail = head, p = trail.next;
                 p != null;
                 trail = p, p = p.next) {
                if (o.equals(p.item)) {
                    unlink(p, trail);
                    return true;
                }
            }
            return false;
        } finally {
            fullyUnlock();
        }
    }

fullyLock()和fullyUnlock()会对putLock()和takeLock()统一上锁或者解锁,这是因为LinkedBlockingQueue是一个双向链表,remove可能会同时影响入队和出队操作

    void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }
    void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }

⑥take() 不停尝试出队,与put()方法类似,会一直尝试出队操作

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            // 如果c>1表示队列非空,当c=1时,此时count=1后进行原子自减,队列为空
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        // 当c等于最大容量时,count等于最大容量然后自减,此时队列非满,唤醒等待队列非满的线程
        if (c == capacity)
            signalNotFull();
        return x;
    }

⑦poll() 和 poll(long, TimeUnit)
这两个方法与offer()和offer(E, long, TimeUnit)类似,poll()只会尝试一次出队,失败返回null,poll(long, TimeUnit)会不停的尝试出队,超时后返回null,这两个方法的源码逻辑跟入队差不多,只贴出来不做解析了。

    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;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

    public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

⑧peek() 查看队头元素,注意是查看,不是取出,源码很简单,没什么可注释的

    public E peek() {
        if (count.get() == 0)
            return null;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            Node<E> first = head.next;
            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            takeLock.unlock();
        }
    }

总结

ArrayBlockingQueue有界阻塞队列和LinkedBlockingQueue无界阻塞队列对比:

相同点:
1、LinkedBlockingQueue和ArrayBlockingQueue都是可阻塞的队列,内部都是使用锁ReentrantLock和对象监视器Condition来保证生产和消费的同步。
2、当队列为空,出队线程即消费者被阻塞;当队列已满,入队线程即生产者被阻塞。

不同点:
1、锁机制不同
  LinkedBlockingQueue中的锁是分离的,入队锁(生产者)putLock,出队锁(消费者)takeLock,而ArrayBlockingQueue出队和入队都使用同一把锁。
2、底层实现不同
  LinkedBlockingQueue内部维护的是一个链表结构。在生产和消费的时候,需要创建Node对象进行插入或移除,大批量数据的系统中,其对于GC的压力会比较大。而ArrayBlockingQueue内部维护了一个数组在生产和消费的时候,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例。
3、构造初始容量不同
  LinkedBlockingQueue有默认的容量大小:Integer.MAX_VALUE,当然也可以传入指定的容量大小,ArrayBlockingQueue在初始化的时候,必须传入一个容量大小的值
4、队列元素个数count定义不同
  LinkedBlockingQueue中定义的count是AtomicInteger类型,ArrayBlockingQueue中定义的count是int类型。

你可能感兴趣的:(JDK源码)