ArrayBlockingQueue源码解析----------干戈的想法

ArrayBlockingQueue

我们都知道这是一个队列,之前对这个类了解的不多,后来看了源码之后才发现与自己想的不一样。本文带大家了解ArrayBlockingQueue的工作原理。首先我们来了解这个类的成员们。

成员

	// 存放元素的数组
	final Object[] items;
	// 下面这两个下标后面再说
	int takeIndex;
	int putIndex;
	// count表示队列中的元素个数
	int count;
	final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;

构造函数

初始化一个数组队列,要确定数组的大小。队列的大小一旦确定了,后面就不会在更改了。只能存放这么多的元素。

// 还记得上面ReentrantLock lock这个成员吗。这是一种锁,他里面分为公平锁
// 和非公平锁,fair这个参数就是用来确定用户选择的是公平锁还是非公平锁。本
// 文对ReentrantLock 这个类不做过多解释,有兴趣的可以去看下他的源码。
   public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

这个构造函数在上面的基础上,将传递进来的元素集合,里面的元素一个一个加到队列里。

    public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        lock.lock(); // 加锁
        try {
            int i = 0;
            try {
                for (E e : c) {
                	// 检查元素是否为null,如果是null会抛出异常
                    checkNotNull(e); 
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
            // putIndex就是最后一个元素的下一个空间的下标,如果元素已经
            // 将队列放满了,那么putIndex从0开始。
        } finally {
            lock.unlock(); //打开锁
        }
    }

添加一个元素

add(E e) 方法和 offer(E e)方法

我们用队列添加一个元素,调用add(E e)方法,其实调用的就是offer(E e)方法。

    public boolean add(E e) {
        return super.add(e);
    }

下面这个就是ArrayBlockingQueue父类的add(E e)方法,可以看到通过调用ArrayBlockingQueue的add方法实际上是调用了自己的offer方法。

    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
    public boolean offer(E e) {
        checkNotNull(e); // 元素不能为null
        final ReentrantLock lock = this.lock;
        lock.lock(); // 上锁,保证了线程安全
        try {
        // 元素的个数不能超过队列的大小,若是队列放满了,那么直接返回false,不再继续存放元素。
            if (count == items.length)
                return false;
            else {
            // 队列没有放满,那么继续存放元素。
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();// 打开锁
        }
    }

// 入队列,将元素放入队列中,如果队列已满那么putIndex为0;
    private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        // 这个方法的作用是唤醒,说这个就要引出另一个添加元素的方法,看下面
        notEmpty.signal();
    }

put(E e)方法

同样是添加元素,那么put和上面的有什么不同呢。通过下面的代码,我们可以发现,最大的变化就是当队列满了之后,不再是返回false,而是将添加元素的线程阻塞了。只能等待别人的唤醒。

当队列满了之后,其后想要添加元素的线程都会被阻塞。只有等到某一个线程删除了队列里的元素,并唤醒阻塞队列。使得程序继续运行。

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

删除一个元素

队列的删除元素就是从头开始一个一个删除。

poll()

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

删除元素,需要靠takeIndex这个下标来删除。takeIndex从0开始,删一个下标加1。当下标走到最后,又从0开始。

每删除一个元素,就会去唤醒一次阻塞队列

    private E dequeue() {
        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;
    }

take()

这个方法也是删除元素,它与上面不同的地方就是,如果队列为空,某个线程要删除元素,那么这个线程就会被阻塞。

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

ps:唤醒有两种唤醒形式。
一种是因为删除时,队列为空而被阻塞的线程
一种是因为添加时,队列已满而被阻塞的线程
两种唤醒与阻塞相互对应。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值