Java BlockingQueue 源码分析

本文详细解析了Java concurrent包中的BlockingQueue概念及其关键API,包括如何通过多种插入删除方法管理线程安全的阻塞队列,以及内部实现机制如锁和条件变量的应用。同时对比了ArrayBlockingQueue的线程安全实现方式,深入探讨了offer vs put和poll vs take的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

BlockingQueue 是 Java concurrent包提供的多线程安全的阻塞队列,其子类包括 LinkedBlockingQueue 和 ArrayBlockingQueue。

关键API

说到队列,自然少不了首尾的插入删除操作,BlockingQueue的API中提供了好几种插入删除方法。
这些方法在遇到无法满足的执行条件时,如队列满了(添加元素时)/队列为空(取出元素时),会采取不同的措施:抛出异常,返回false/null,阻塞调用API的线程,等待一定时间等。具体如下表:

Throws exceptionSpecial valueBlocksTimes out
Insertadd(e)offer(e)put(e)offer(e,time,unit)
Removeremove()poll()take()poll(time,unit)
Examineelement()peek()not applicablenot applicable

ArrayBlockingQueue

ArrayBlockingQueue是一个基于数组的阻塞队列,在创建一个ArrayBlockingQueue时,需要提供的一个表示队列大小的参数。

ArrayBlockingQueue是线程安全的,但这是怎么做到的呢?这需要注意类中的三个属性:

    /** Main lock guarding all access */
    final ReentrantLock lock;
    /** Condition for waiting takes */
    private final Condition notEmpty;
    /** Condition for waiting puts */
    private final Condition notFull;

这里一个锁,两个条件变量,管理所有使用API的线程的互斥和同步。具体的锁和条件变量的理论知识,可以参见相关的操作系统书籍。

offer vs put
    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                insert(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            insert(e);
        } finally {
            lock.unlock();
        }
    }

可以看到,两个方法中在往队列里添加元素时,都是先对临界区加锁,不同在于,offer方法中若是检测出队列已满,会直接返回false;put方法中,若是检测出队列已满,线程会在 notFull 条件变量中阻塞,这样线程会释放锁,让其他线程进入临界区。以后某个时间,一个线程从队列中取出元素,队列不再为空,并且该线程唤醒在notFull条件变量上阻塞的线程,put方法才有可能完成。

poll vs take
 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return extract();
        } finally {
            lock.unlock();
        }
    }

take的逻辑和put差不多,只不过这次是若队列为空,则线程阻塞在notEmpty 条件变量上,等待其他线程往队列中添加元素,并唤醒在notEmpty上阻塞的线程。

   public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : extract();
        } finally {
            lock.unlock();
        }
    }

poll方法在检测到队列为空时,直接返回null,否则取出队头元素。

      public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return extract();
        } finally {
            lock.unlock();
        }
    }

poll 还有个比较有趣的重载实现,这里利用Condition变量的计时器方法awaitNanos 。先将时间大小根据时间单位换算成纳秒的数值,当队列容量为0是,使用Condition.awaitNanos(…),进行计时,超时后返回null。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值