堵塞队列之ArrayBlockingQueue和LinkedBlockingQueue解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25673113/article/details/79304268

在线程池创建的时候,需要传一个堵塞队列来维护需要执行的线程任务,其中最常用的是ArrayBlockingQueue和LinkedBlockingQueue。他们都继承了BlockingQueue接口。
这里写图片描述

ArrayBlockingQueue

一个有边界的堵塞队列,内部使用了一个队列来保存元素,有takeIndex和putIndex来维护队列头和尾部的游标。

    /** The queued items */
    //保存元素的数组
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    /** Number of elements in the queue */
    int count;

    /** Main lock guarding all access */
    //在取和存元素的时候会进行加锁
    final ReentrantLock lock;

    /** Condition for waiting takes */
    //在take的时候,如果数组为空,进行堵塞,直到数组不为空
    private final Condition notEmpty;

    /** Condition for waiting puts */
    //在poll的时候,如果数组满了,进行堵塞,直到数组有空位
    private final Condition notFull;

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        //使用锁,默认的会使用ReentrantLock的公平锁
        //公平锁不先尝试获取锁,直接放入AQS等待队列里,不公平锁会先尝试是否可以获取锁,失败才会进入等待队列(具体实现可以查看ReentrantLock源码)
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

添加数据可以使用offer和put方法,两个区别是offer是实现Queue的方法,在队列满时候返回false,不进行其他操作。而put是实现了父类BlockingQueue的方法,在队列满的时候会通过notFull.await();进行堵塞,知道队列有空位的时候后再进行判断。
删除也是同理,poll是实现Queue的方法,在队列为空的时候不进行任何操作,take是实现BlockingQueue的方法,在队列空的的时候会进行堵塞。

    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                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,要不堵塞后还是需要进行判断
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
    //取出数据
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

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

当队列添加数据的时候,先会上锁,然后调用enqueue方法添加元素。
当队列删除数据的时候,还是会先上锁,然后调用dequeue删除元素,其中会把items[takeIndex]置为null,这样有助于gc,防止内存溢出。
其中putIndex和takeIndex和最大的长度一致时,会变为0,形成一个环,使队列可以循环使用。

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

LinkedBlockingQueue

LinkedBlockingQueue和ArrayBlockingQueue相比最大的不同是他可以作为一个无边界的堵塞队列,他的内部使用的链表的形式来保存元素。

//内部定义了一个很简单的Node节点来保存数据。
static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

其次,其内部分别定义了读写锁来对写和读进行加锁操作,这样相比一个锁的好处是细化了锁的跨度,读写分离,减小了锁的竞争。

    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

思考了下为什么ArrayBlockingQueue只用了一个锁的?我的理解是LinkedBlockingQueue是由链表组成操作的分别是头尾节点,相互竞争的关系较小。而ArrayBlockingQueue是数组,添加和删除都是在同一个数组上,虽然也可以用两个锁但是实现上需要更多的控制。

猜你喜欢

转载自blog.csdn.net/qq_25673113/article/details/79304268