Java并发编程--并发队列原理之ArrayBlockingQueue

ArrayBlockingQueue原理探究

​ ArrayBlockingQueue是使用有界数组方式实现的阻塞队列.

(1). 结构

在这里插入图片描述
​  ArrayBlockingQueue内部有一个数组items,用来存放队列元素.outIndex用来存放入队元素下标,tackIndex用来存放出队元素下标.这些变量并没有使用volatile修饰,因为加锁已经保证了这些变量在锁内的可见性了.

​  独占锁lock用来保证出入队操作的原子性,notEmpty,notFull连个条件变量用来进行出入队的同步.

​  ArrayBlockingQueue是有界的,所以构造必须传入队列大小为参数.默认情况下使用非公平锁.

(2). ArrayBlockingQueue原理介绍

1). offer操作

​  插入元素,如果队列已满,则丢弃元素,不会阻塞线程.

public boolean offer(E e) {
    // 判断元素是否为空,为空抛出异常
    checkNotNull(e);
    // 加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 判断队列是否满,count为队列中已填充元素数量
        // items.length为数组长度,也就是队列的最大值
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
// 方法内部将在putIndex位置上放置新元素,并将putIndex++,如果越界重置为0(循环数组)
private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    notEmpty.signal();
}

2). put操作

​  向尾部插入一个元素,如果有空闲,插入,如果队列已满,阻塞等待队列出现空闲.

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

3). poll操作

​  从队列头部移除一个元素,如果队列为空,返回null,不会阻塞等待队列不为空

public E poll() {
    // 加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 如果队列为空,返回null
        // 如果不为空,进行删除元素的操作,并将该元素返回
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();//解锁
    }
}
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--;
    // itrs是当前活动迭代器的共享状态;如果已知没有状态,则为null。
    if (itrs != null)
        // 更新迭代器中的元素数据
        itrs.elementDequeued();
    // 唤醒因为队列满导致没有入队成功的入队线程
    notFull.signal();
    return x;
}

4). take操作

​  获取当前队列头部元素,并删除它,如果队列为空,会阻塞等待队列不为空时进行操作

​  可响应中断.

public E take() throws InterruptedException {
    // 获取锁对象,并响应中断式的加锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 循环判断队列是否为空,为空则条件阻塞
        while (count == 0)
            notEmpty.await();
        // 删除并返回头部元素
        return dequeue();
    } finally {
        lock.unlock();
    }
}

5). peek操作

​  获取头部元素,但是不移除,如果队列为空,返回null

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 直接返回出队下标处的元素
        return itemAt(takeIndex);
    } finally {
        lock.unlock();
    }
}
// 获取对应下表处的元素
final E itemAt(int i) {
    return (E) items[i];
}

(3). 小结

​  ArrayBlockingQueue使用一个独占锁来实现只能有一个线程进行入队和出队操作,这个锁的粒度比较大,类似于在方法上加synchronized.

发布了141 篇原创文章 · 获赞 47 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41596568/article/details/104076239