【JAVA多线程】ArrayBlockingQueue 源码分析

在这里插入图片描述

ArrayBlockingQueue

由数组支持的有界阻塞队列。此队列对元素进行 FIFO(先进先出)排序。队列的 头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素被插入到队列的尾部,队列检索操作获取队列头部的元素。

这是一个经典的“有界缓冲区”,其中一个固定大小的数组保存由生产者插入并由消费者提取的元素。一旦创建,容量将无法更改。尝试将put一个元素放入一个完整的队列将导致操作阻塞;尝试take从空队列中获取元素同样会阻塞。

此类支持对等待的生产者和消费者线程进行排序的可选公平策略。默认情况下,不保证此排序。但是,使用公平设置构造的队列以trueFIFO 顺序授予线程访问权限。公平性通常会降低吞吐量,但会降低可变性并避免饥饿。

用法

在实际应用中,设置数组大小时要充分考虑到数据量,如果设置值过小,容易造成堵塞,而且运行中无法修改大小。不支持null元素,否则报NullPointerException异常。

import java.util.concurrent.*;

public class Test {

    public static void main(String[] args) {
        final BlockingQueue queue = new ArrayBlockingQueue(3);
        for(int i=0;i<2;i++){
            new Thread(){
                public void run(){
                    while(true){
                        try {
                            Thread.sleep((long) (Math.random()*1000));
                            System.out.println(Thread.currentThread().getName() + "准备放数据!");
                            queue.put(1);
                            System.out.println(Thread.currentThread().getName() + "已经放了数据," +
                                    "队列目前有" + queue.size() + "个数据");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }

            }.start();
        }

        new Thread(){
            public void run(){
                while(true){
                    try {
                        //将此处的睡眠时间分别改为100和1000,观察运行结果
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName() + "准备取数据!");
                        queue.take();
                        System.out.println(Thread.currentThread().getName() + "已经取走数据," +
                                "队列目前有" + queue.size() + "个数据");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }.start();
    }


}

成员变量

ArrayBlockingQueue是基于数组的队列,锁是基于ReentrantLock。

final Object[] items; //队列数组,循环数组
int takeIndex;//下次出队下标(take、poll、remove),队列满了会重置为0
int putIndex;//下次入队下标(pull、offer、add)
int count;// 队列中元素个数

final ReentrantLock lock;//锁
private final Condition notEmpty;// 当队列为空时,就会调用notEmpty的wait方法,让当前线程等待
private final Condition notFull;//等待条件(入队)

构造函数

  • 由于公平锁会降低队列的性能,因而默认使用非公平锁
  • 实例化ArrayBlockingQueue必须要设置数组长度,这就是为什么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); //初始化锁对象,fair代表是否公平,公平就是FIFO
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

生产元素

offer

如果可以在不超过队列容量的情况下立即插入指定元素,则在此队列的尾部插入指定元素,true成功时返回并且false如果此队列已满。

public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;//如果队列已满,返回false
            else {
                enqueue(e);//入队,返回true
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;向入队下标数组设置元素
        items[putIndex] = x;
        //如果放入这个元素后队列满了,入队下标设置为0
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal(); //唤醒堵塞的消费线程
    }

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

put和offer方法差别基本不大,只是put方法通过 notFull.await(),一直堵塞,直至被唤醒

消费元素

poll

检索并删除此队列的头部,如果此队列为空,则返回null

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();//如果此队列为空,则返回`null`
        } 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];//通过出队下标获取元素
        items[takeIndex] = null;
        if (++takeIndex == items.length) //消费到队列为空了,重置为0,下次又从头位置消费
            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();
        }
    }

总结

修饰符和类型 方法及说明
boolean add(E e)如果可以在不超出队列容量的情况下立即插入指定元素,则在此队列的尾部插入指定元素,成功返回并在此队列已满true时抛出一个 。IllegalStateException
void clear()原子地从此队列中删除所有元素。
boolean contains(Object o)如果此队列包含指定的元素,则返回true。
Iterator<E> iterator()以正确的顺序返回此队列中元素的迭代器。
boolean offer(E e)如果可以在不超过队列容量的情况下立即插入指定元素,则在此队列的尾部插入指定元素,true成功时返回并且false如果此队列已满。
boolean offer(E e, long timeout, TimeUnit unit)在此队列的尾部插入指定元素,如果队列已满,则等待指定的等待时间以使空间可用。
E peek()检索但不删除此队列的头部,null如果此队列为空,则返回。
E poll()检索并删除此队列的头部,null如果此队列为空,则返回。
E poll(long timeout, TimeUnit unit)检索并删除此队列的头部,如果有必要等待指定的等待时间以使元素可用。
void put(E e)在此队列的尾部插入指定元素,如果队列已满,则等待空间可用。
boolean remove(Object o)从此队列中移除指定元素的单个实例(如果存在)。
int size()返回此队列中的元素数。
E take()检索并删除此队列的头部,如有必要,等待元素可用。
Object[] toArray()按正确的顺序返回包含此队列中所有元素的数组。
<T> T[] toArray(T[] a)按正确的顺序返回包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。
String toString()返回此集合的字符串表示形式。

猜你喜欢

转载自blog.csdn.net/qq_15604349/article/details/124498595