LinkedBlockingQueue源码分析

概述

  1. 基于单链表实现的队列,队列最大节点容量可限定(默认容量为Interge.MAX_VALUE);
  2. 这个队列是阻塞队列,意味着当入队或出队操作不满足条件时或可抛异常、或可返回特殊值(通常null或false)、或可阻塞当前线程直至操作完成、或可在设定的超时时间到来时放弃操作;
  3. 队列元素FIFO(先进先出),也即链表尾部入队,头部出队;
  4. 只允许一个线程入队或出队,但是入队和出队操作可同时进行;
  5. 相比基于数组的阻塞队列(如ArrayBlockingQueue)拥有更高的吞吐量,但在大部分并发应用中可预测性能要差些;
  6. 不允许入队元素为null。

源码分析

继承关系和内部成员

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 链表上的节点
    static class Node<E> {
        E item; // 节点存储put或offer对象

        Node<E> next; // 下个节点引用

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

    // 记录队列上链接节点容量
    private final int capacity;

    // 记录链表节点总量
    private final AtomicInteger count = new AtomicInteger();

    // 记录链表头节点,节点上item常为null
    transient Node<E> head;

    // 记录链表尾节点,节点上next常为null
    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();

    ...
}

总结:

  • BlockingQueue的实现类
  • 内部成员记录队列节点数量、队列头/尾节点、队列最多允许节点数量;
  • 两把锁:一把入队锁,一把出队锁
构造函数
    public LinkedBlockingQueue() { // 无参构造对象
        this(Integer.MAX_VALUE);// 队列容量默认Integer.MAX_VALUE
    }

    public LinkedBlockingQueue(int capacity) { // 用指定容量构造对象
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null); // 注意节点item为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); // 将最终的入队节点数量保存到count成员中
        } finally {
            putLock.unlock();
        }
    }

总结:

  • 三个构造函数,第二个可限制链表节点数量,其他两个链表节点数量容量为Integer.MAX_VALUE;
  • 前两个构造函数调用后,head和last被赋值,且相关对象如下图(第三个构造函数待入队源码分析后自然知晓):

put或offer入队操作

入队方法主要有如下几个方法:

public void put(E e) throws InterruptedException;
public boolean offer(E e);
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;

拿最后一个方法的源码进行分析,其他两个类似:

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

        if (e == null) throw new NullPointerException();
        // 计算入队操作阻塞时允许等待的超时时间
        // 从后面代码看如果当前队列节点数量没满容量限制,timeout形参也是可以设0和负值的,此时退化成boolean offer(E e)方法
        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 <= 0L) return false; // 不超时等待入队失败
                // 调用putLock关联的condition的awaitNanos()释放入队锁,线程休眠
                nanos = notFull.awaitNanos(nanos); // 等待入队通知,超时时间到时还没通知入队该方法抛InterruptedException并退出
            }
            enqueue(new Node<E>(e)); // 新建节点并把该节点入队
            c = count.getAndIncrement(); // count自增1,注意返回值c是count自增1前的count值
            if (c + 1 < capacity) // 队列节点数还没达到限制值
                notFull.signal();  // 这个有点意思,可能其他线程put或offer时await
        } finally {
            putLock.unlock();
        }
        if (c == 0) { // 入队前队列节点数为0,需要获取出队锁唤起出队休眠线程
            // signalNotEmpty(); 源码调用该函数,为了方便直接注释,然后贴上该函数实际代码
            // /* 下面是signalNotEmpty()的实际代码
            final ReentrantLock takeLock = this.takeLock;
            takeLock.lock();
            try {
                notEmpty.signal();
            } finally {
                takeLock.unlock();
            }
            // signalNotEmpty()的实际代码结束 */
        }
        return true;
    }

offer方法中enqueue方法定义以及offer函数执行完后链表情况以及head、last指向如下图所示:

take或poll出队操作

出队方法主要有如下几个方法:

public E take() throws InterruptedException;
public E poll();
public E poll(long timeout, TimeUnit unit) throws InterruptedException

拿take方法进行源码分析:


    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count; // 获取当前count数量
        final ReentrantLock takeLock = this.takeLock; // 获取出队锁
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) { // 链表为空释放出队锁,线程休眠
                notEmpty.await(); // signalNotEmpty()函数会唤醒退出await()
            }
            x = dequeue(); // 出队
            c = count.getAndDecrement(); // count自减1,并返回count自减1前的count值
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity) // 出队前队列节点已满限制
            signalNotFull(); // 获取入队锁并唤醒等待的入队Condition
        return x;
    }

take方法中dequeue方法定义以及take方法执行完后链表以及head/last引用情况如下图所示:

后言

讲真,这个代码还是蛮简单的,之所以写它其实为了mark下,同时心里其实也惦记着写篇加强版的LinkedBlockingDeque简述,故而该文也算是个铺垫。
如果对ReentrantLock和对应的Condition相关函数有不懂,可以参考本人先前写的Lock框架简介

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值