LinkedBlockingQueue 源码分析

前言

  1. 本文基于 jdk 8 编写。
  2. @author JellyfishMIX - github / blog.jellyfishmix.com
  3. LICENSE GPL-2.0

offer(E e) 方法[非阻塞]

offer 方法呈现的效果: offer(E e): 表示如果可能的话,将 e 添加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false。本方法不阻塞当前执行方法。

	/**
     * Current number of elements
     * 队列的当前元素个数
     */
    private final AtomicInteger count = new AtomicInteger();

	/**
     * The capacity bound, or Integer.MAX_VALUE if none
     * 队列的容量上限
     */
    private final int capacity;

	/**
     * Lock held by put, offer, etc
     * 写锁,put, offer 方法共用这把锁。可以理解为添加元素时用的写锁
     */
    private final ReentrantLock putLock = new ReentrantLock();

	/** 
     * Wait queue for waiting puts
     * 等待添加元素的线程
     */
    private final Condition notFull = putLock.newCondition();

	/**
     * Linked list node class
     * 链表实现的队列,单个存储单元
     */
    static class Node<E> {
        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)
         */
        Node<E> next;

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

	/**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and {@code false} if this queue
     * is full.
     * When using a capacity-restricted queue, this method is generally
     * preferable to method {@link BlockingQueue#add add}, which can fail to
     * insert an element only by throwing an exception.
     *
     * @throws NullPointerException if the specified element is null
     */
	public boolean offer(E e) {
        // 判空校验
        if (e == null) throw new NullPointerException();
        // 队列的当前元素个数
        // 1. 把成员变量 count 赋值给了局部变量 count 2. 局部变量用 final 修饰了
        final AtomicInteger count = this.count;
        // 如果当前队列的元素个数已达容量上限,则直接返回 false
        if (count.get() == capacity)
            return false;
        // 表示添加元素后,队列的当前元素个数
        int c = -1;
        // node,新建一个存储单元
        Node<E> node = new Node<E>(e);
        // 当前的写锁,put, offer 方法共用这把锁。可以理解为添加元素时用的写锁
        final ReentrantLock putLock = this.putLock;
        // 加锁,进入线程安全区
        putLock.lock();
        try {
            // 在加锁后的线程安全区,判断队列中是否还有剩余容量
            if (count.get() < capacity) {
                // 入队列
                enqueue(node);
                // 队列容量 + 1
                c = count.getAndIncrement();
                // 如果添加元素后,队列仍然没有满,则唤醒一个等待添加元素的线程
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            // 解锁
            putLock.unlock();
        }
        // 如果添加元素后,队列非空,则唤醒一个等待拿取(take)的线程
        if (c == 0)
            signalNotEmpty();
        // 表示是否添加成功
        return c >= 0;
    }

额外的注释:

// 队列的当前元素个数
final AtomicInteger count = this.count;

这里值得关注的问题有两处:

  1. 把成员变量 count 赋值给了局部变量 count
  2. 局部变量用 final 修饰了

回答 1. 这里把成员变量 count 赋值给了局部变量 count。访问成员变量,每次都需要读内存。但是局部变量可以放入 cpu 缓存,这样先把成员变量赋值给局部变量,读 cpu 缓存的时间会快于读内存。不过这种把成员变量先读成局部变量的方式,属于极端优化。正常写业务不用这么写,还可能增加心智负担。性能差异不大,不如考虑开发效率,降低心智负担。

回答 2.

image-20220715144828474

image-20220715144852239

我们先来看两张图的对比,可以发现对局部变量赋值的操作,加与不加 final,编译后的字节码是相同的。字节码里没有任何东西能体现出局部变量的 final 与否,Class 文件里除字节码(Code 属性)外的辅助数据结构也没有记录任何体现 final 的信息。既然带不带 final 的局部变量在编译到 Class 文件后都一样了,其访问效率必然一样高,JVM 不可能有办法知道什么局部变量原本是用 final 修饰来声明的。

(题外话: 有一个例外,编译常量时,final 常量折叠机制: JVM对于声明为final的局部变量(local var)做了哪些性能优化? - RednaxelaFX的回答 - 知乎

猜测,截图中,jdk 源码里作者加 final 修饰,应该是为了避免自己手滑在后面改写了局部变量里最初读到的值,而加上 final 来让编译器(javac 之类)检查。

put(E e) 方法[阻塞]

put 方法呈现的效果:put(E e) 把 e 加到 BlockingQueue 里,如果 BlockingQueue 没有空间,则调用此方法的线程被阻塞直到BlockingQueue 里面有空间再继续。

	/**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary for space to become available.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    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,新建一个存储单元
        Node<E> node = new Node<E>(e);
        // 当前的写锁,put, offer 方法共用这把锁。可以理解为添加元素时用的写锁
        final ReentrantLock putLock = this.putLock;
        // 队列的当前元素个数
        final AtomicInteger count = this.count;
        // 后面 put 方法可能会阻塞住当前线程,因此加一个可中断的锁。进入线程安全区
        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 (count.get() == capacity) {
                notFull.await();
            }
            // 入队列
            enqueue(node);
            // 队列容量 + 1
            c = count.getAndIncrement();
            // 如果添加元素后,队列仍然没有满,则唤醒一个等待添加元素的线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            // 解锁
            putLock.unlock();
        }
        // 如果添加元素后,队列非空,则唤醒一个等待拿取(take)的线程
        if (c == 0)
            signalNotEmpty();
    }

poll() 方法

	/**
     * Lock held by take, poll, etc
     * 读锁,take, poll 方法共用这把锁。可以理解为拿取元素时用的读锁
     */
    private final ReentrantLock takeLock = new ReentrantLock();
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JellyfishMIX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值