ArrayBlockingQueue阻塞队列实现分析

目录

基本认识:

conditon.await()和condition.signal()

整体流程

生产者通过put生产元素:blockingQueue.put()

消费者通过take消费元素: blockingQueue.take()


基本认识:

  • 生产者去生产和消费者去消费都需要拿到此锁,拿不到就进入lock的AQS队列:ReerantLock lock
  • 消费者去消费的条件队列:Condition notEmpty
  • 生产者继续生产的条件队列:Condition notFull
  • putIndex(每一轮都是从0-array.length,走到array.length后,继续从0开始下一轮):下一个生产者生产的位置
  • takeIndex(每一轮都是从0-array.length,走到array.length后,继续从0开始下一轮):下一个消费者消费的位置
  • count:当前数组阻塞队列里元素的个数(由于生产者不断生产和消费者不断消费,所以大部分是不连续的元素)
  • items:数组阻塞队列

conditon.await()和condition.signal()

ReetnrantLock lock
condition=lock.newCondition


condition.await(每个condtion内部都有自己的一个队列):

  1. 会释放lock锁
  2. 将当前线程从AQS队列移除,添加到条件队列contion尾部
  3. 调用Locksupport.park()进行阻塞(阻塞在await方法内部)


condition.signal()

  1. 唤醒condition队列的头结点 添加到AQS队列,
  2. 解除阻塞Locksupport.unpark()
  3. 此时执行await方法里的自旋获取锁的方法,获取成功后,从conditon队列移除,await执行完毕。原线程接着从await的下一行代码开始执行,实现了唤醒。

整体流程

生产者生产和消费者消费都是在加锁基础上进行的,所以线程安全:

  • 生产者从【0-数组长度】循环生产元素,成功生产一个就唤醒消费者来消费,数组被占满后调用await阻塞(此时释放锁,从AQS队列移除,添加到notFull队列尾部),等待着消费者唤醒自己继续生产
  • 消费者从【0-数组长度】循环消费元素,成功消费一个就唤醒生产者可以生产了,数组没有元素时,消费者调用await阻塞自己(此时释放锁,从AQS队列移除,添加到notEmpty队列尾部)),等待着生产者唤醒自己继续消费。
  • 唤醒过程:从条件队列移除,重新进入AQS队列,自旋获取锁,获取锁成功后继续生产或消费

生产者通过put生产元素:blockingQueue.put()

  1. 首先需要获取到锁lock.lock()
  2. 获取锁后判断当前阻塞队列是否已满:count == items.length
  • 可以生产:把元素放到items[putIndex]=put的元素,队列元素个数+1,下一个应放的位置+1 putIndex++,如果putIndex+1后=数组长度,则重新置putIndex=0,成功生产一个最后需要唤醒消费者:notEmpty.signal(),之所以唤醒消费者是因为此时队列里至少有一个元素了不满足isEmpty,符合NotEmpty,唤醒NotEmpty队列的第一个节点,将其移动到AQS队列,解除阻塞,重新获取锁,获取成功后,从notEmpty队列移除,继续await去消费
  • 不可以生产:调用notFull.await()【此时已满,不满足不满notFull,所以notFull要等待,notFull.await()】调用此方法后会释放锁而从AQS队列移除,此时被添加到条件队列notFull尾部,然后调用LockSupport.park进行阻塞等待着消费者成功消费一个后唤醒自己,此时消费者会调用notFull.signal()唤醒notFull队列的第一个节点,进入可以生产
 public void put(E e) throws InterruptedException {
        //检查元素是否为null,如果是,抛出NullPointerException
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        try {
            //如果队列已满,阻塞,等待队列成为不满状态
            while (count == items.length)
                notFull.await();
            //将元素入队
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

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();
    }


消费者通过take消费元素: blockingQueue.take()

  1. 首先需要获取到锁lock.lock()
  2. 获取锁后判断当前阻塞队列是否有元素可以消费:count == 0
  • 可以消费:items[takeIndex]元素返回给消费者,并且items[takeIndex]置为null,队列元素个数-1,takeIndex+1,同理takeIndex达到数组长度后,同样重新从0开始加到数组长度,成功消费一个后(消费了一个现在肯定不满了,生产者可以继续生产了),调用notFull.signal()唤醒生产者notFull队列的第一个节点firstWaiter,将firstWaiter重新添加到AQS队列,解除阻塞LockSupport.unPark,自选获取锁,获取成功后,从条件队列删除,继续从await那里继续向下生产
  • 没有可消费的:调用notEmpty.await()阻塞,释放锁,从AQS队列移除,添加到notEmpty队列尾部,等待生产者成功生产一个后调用notEmpty.signal唤醒自己,然后进入可以消费的步骤。
     public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            //首先加锁
            lock.lockInterruptibly();
            try {
                //如果队列为空,阻塞
                while (count == 0)
                    notEmpty.await();
                //队列不为空,调用dequeue()出队
                return dequeue();
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    
    private E dequeue() {
            // assert lock.getHoldCount() == 1;
            // assert items[takeIndex] != null;
            final Object[] items = this.items;
            @SuppressWarnings("unchecked")
            //取走数据
            E x = (E) items[takeIndex];
            //置为null,以助gc
            items[takeIndex] = null;
            //循环取
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            if (itrs != null)
                itrs.elementDequeued();
            //通知因队列满而阻塞的线程
            notFull.signal();
            return x;
        }

     

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值