阻塞队列ArrayBlockingQueue源码解读

3 篇文章 0 订阅
3 篇文章 0 订阅

阻塞队列ArrayBlockingQueue源码解读

目录

阻塞队列ArrayBlockingQueue源码解读

前言

源码解读


前言

BlockingQueue,是java.util.concurrent 包提供的用于解决并发生产者 - 消费者问题的最有用的类,它的特性是在任意时刻只有一个线程可以进行take或者put操作,并且BlockingQueue提供了超时return null的机制,在许多生产场景里如线程池都可以看到这个工具的身影。

常见的4种阻塞队列:

  • ArrayBlockingQueue 由数组支持的有界队列
  • LinkedBlockingQueue 由链表支持的可选有界队列
  • PriorityBlockingQueue 由优先级堆支持的无界优先级队列
  • DelayQueue 由优先级堆支持的、基于时间的调度队列

下面以ArrayBlockingQueue为例,介绍其工作原理。


源码解读

ArrayBlockingQueue通过Reentantlock实现同步机制,并通过Condition实现条件等待。首先看一下ArrayBlockingQueue的属性变量:

    /** 内部维护的队列节点数组 */    
    final Object[] items;

    /** 下一个take操作的数组下标 */
    int takeIndex;

    /** 下一个put操作的数组下标 */
    int putIndex;

    /** 队列中的节点个数 */
    int count;


    /** 队列同步锁 */
    final ReentrantLock lock;

    /** take操作等待条件 */
    private final Condition notEmpty;

    /** put操作等待条件 */
    private final Condition notFull;

进入put操作源码:

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        /**往队列放元素前先加锁,这里用了lockInterruptibly,表示可响应中断*/
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();  //如果队列满了,则在notFull这个条件上等待,即进入条件队列
            enqueue(e);  //被唤醒后,继续put操作,入队
        } finally {
            lock.unlock();
        }
    }

 下面进入notFull.await()-->AbstractQueuedSynchronizer.await():

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();  //创建一个node放入条件等待队列
            int savedState = fullyRelease(node);  //释放state资源
            int interruptMode = 0;
            
            /**
            *这里判断是否在同步队列中,如果不在同步队列中会无限被阻塞,当然这里node刚被创建进入  
            *条件队列,肯定不在,进入循环,线程被挂起,等待被唤醒                      
            */
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            /**
            *这里是不是很熟悉acquireQueued()这个方法也被Reentlock复用了,用于同步队列中的节
            *点尝试获取锁
            */
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();  //剔除无效节点
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode); //这里interruptMode用于接收该节点是否是由于signal或是中断唤醒的,如果是中断唤醒的,会做相应处理
        } 

说一下acquireQueued(node, savedState)这个方法,在之前的Reentlock源码中有详细介绍(传送门:Reentantlock源码解读_w7sss的博客-CSDN博客),这里就略过了。下面看AbstractQueuedSynchronizer.addConditionWaiter()入条件队列的方法:

        private Node addConditionWaiter() {
            Node t = lastWaiter;
            //如果队尾节点为null或状态不为Node.CONDITION,则为无效节点,直接干掉
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION); //构建条件队列节点
            if (t == null)
                firstWaiter = node; //如果lastWaiter还为null,说明队列为空,直接设为头节点
            else
                t.nextWaiter = node; //入队
            lastWaiter = node;
            return node;
        }

入队成功后,开始释放state资源,AbstractQueuedSynchronizer.fullyRelease():

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {  //这里release释放资源,同Reentlock
                failed = false;
                return savedState;  //返回资源状态
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

释放完state资源后,就会来到await()方法的这个循环,上面提到:

while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
 }

线程在这里被阻塞,直到在condition上被唤醒,node会从条件队列移到同步队列,开始排队获取锁,这里就和Reentantlock中的逻辑一样了,如果拿到了锁,就会回到上面的第一个方法put操作中的enqueue将元素放入阻塞队列:

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal(); //唤醒消费者线程
    }

入队之后,在最后唤醒消费者线程。进入notEmpty.signal()--->AbstractQueuedSynchronizer.doSignal():

        private void doSignal(Node first) { //first为条件队列的头节点
            do {
                //do循环体:如果条件队列中只有firstWaiter一个节点了,则完全清空条件队列
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) && (first = firstWaiter) != null);
        }

其中transferForSignal(first)把条件队列的头节点移到同步队列中,如果失败则会循环在下一个节点上重试:

    final boolean transferForSignal(Node node) {
        /*
         * cas修改node的状态为0
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * enq执行入同步等待队列操作,返回前驱节点p,如果p的状态>0或者修改p的状态为Node.SIGNAL
         * 失败则直接唤醒node节点。
         * 这里要注意的是,同步队列中的节点能否被唤醒取决于其前驱节点的状态(需要为Node.SIGNAL)
         * 如果成功把前驱节点的状态设置为Node.SIGNAL,那node节点就可以放心地去同步队列中排队了,否则因为将来无法被唤醒,所以只好提前唤醒node
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

至此,put源码结束了,至于take操作,为消费者线程,其原理和生产者基本一样,只是入队换成了出队,就不重复讲解了。

这里我们可以看到,条件队列并不直接参与竞争锁的操作,而是要先转换成同步队列中的节点,才有资格去竞争锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值