阻塞队列ArrayBlockingQueue源码解析

ArrayBlockingQueue基于数组的数据结构实现,在其内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部坐标 takeIndex (消费数据坐标) 和尾部坐标 putIndex (生产数据坐标)。

ArrayBlockingQueue也被称为有界阻塞队列,顾名思义,就是明确有一个队列大小的。因此没有默认的构造函数,没有默认的队列大小。

	// 声明队列大小的构造器 默认为非公平锁
	public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
	
	// 可以指定公平锁或非公平锁的构造器
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        // 初始化数组
        this.items = new Object[capacity];
        // 锁
        lock = new ReentrantLock(fair);
        // 队列不为空时的Condition (相当于 wait(), notify())
        notEmpty = lock.newCondition();
        // 队列未满
        notFull = lock.newCondition();
    }

生产数据,即往队列中(数组中添加一个元素),ArrayBlockingQueue提供了四个方法。

第一个、第二个其实一样,都是offer方法。只不过add方法在队列已满时会抛出异常,根据业务不同可以选择不同方法。

	// 第一个
	public boolean add(E e) {
        // 实际调用的offer方法
        return super.add(e);
    }

	// super.add() 方法
	public boolean add(E e) {
        if (offer(e))
            return true;
        else
        	// 队列已满,则抛异常
            throw new IllegalStateException("Queue full");
    }
	
	// 第二个
	public boolean offer(E e) {
        // 判空
        checkNotNull(e);
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 如果队列已经满了 直接返回添加失败  offer方法不会进行阻塞 put方法才会阻塞
            if (count == items.length)
                return false;
            else {
                // 队列没有满,则正常添加数据
                enqueue(e);
                return true;
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
	

第三个方法,指定等待时间插入元素,如果队列已满,如果指定时间内没有被其他线程唤醒,则插入失败。

	public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 如果队列已满
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                // 等待(挂起)指定时间后,如果没有在指定时间内被其他线程唤醒,则这里返回值小于0,直接返回插入失败
                nanos = notFull.awaitNanos(nanos);
            }
            // 否则插入元素
            enqueue(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();
            // 插入元素
            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();
    }

接下来就是消费数据方法,相应的消费数据也有四个方法。

第一个方法是peek()方法,准确地说这个方法不是消费,而是查询,只是起到查看能消费的第一个数据,并没有从队列中移出元素。

	public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 返回当前队列第一个元素,并不移出,
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }
    
	final E itemAt(int i) {
        return (E)items[i];
    }

第二个方法是poll(),第三个方法是指定了时间的poll(long timeout, TimeUnit unit)方法。当队列为空时,取出的元素为null,不为空时,取出队列第一个元素,并从队列中移出。第三个方法会在队列为空时,等待指定时间再返回消费的结果。

	public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
	
	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;
                // 等待指定时间 如果该时间内没有被唤醒(在插入元素时唤醒,即队列不为空时),则返回null
                nanos = notEmpty.awaitNanos(nanos);
            }
            // 移出元素
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

第四个方法为take(),该方法在队列为空时会阻塞,直到有数据才取出并结束。

	public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                // 为空时 等待
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

其中二三四这三个方法最终调用的都是 dequeue() 这个方法去实现取出并移除元素的。

	private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        // 取出队列第一个元素
        E x = (E)items[takeIndex];
        // 将该元素置空
        items[takeIndex] = null;
        // 如果队列第一个元素坐标为数组最后一个,则重新计算坐标为第一个
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            // 如果迭代器不为空 则从迭代器中删除
            itrs.elementDequeued();
        // 删除一个元素后,唤醒因为队列已满而等待的线程
        notFull.signal();
        return x;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值