阻塞队列--BlockingQueue

301 阅读5分钟

一:简述

今天和大家一起聊一聊阻塞队列,理解阻塞队列的原理实现。

二:什么是阻塞队列

首先阻塞队列是一个队列的数据结构,支持先进先出,其次相对于普通的阻塞队列,阻塞队列拥有两个特性:

1.在队列为空的时候,线程从阻塞队列中获取数据的时候会被阻塞。

2.在队列满了的时候,线程向阻塞队列中添加数据的时候会被阻塞。

三:添加元素

BlockingQueue接口定义了add(),put(),offer()三个添加元素的方法。

add()方法

boolean add(E e);

向队列中添加元素,如果已经达到最大容量,那么抛出异常。

put()方法

void put(E e) throws InterruptedException;

向队列中添加元素,如果已经达到最大容量,那么会阻塞当前线程。

offer()方法

offer()方法有两个重载的方法。

boolean offer(E e, long timeout, TimeUnit unit) throw InterruptedException

向队列中添加元素,如果已经达到最大容量,那么阻塞当前线程,到达超时时间后添加成功 返回true,否则返回false。

boolean offer(E e);

向队列中添加元素,成功返回true,否则返回false。

四:获取元素

BlockingQueue接口定义了take()和poll()两个方法。

take()

E take() throws InterruptedException;

向队列中获取元素,如果队列为空,那么阻塞当前线程。

poll()

E poll(long timeout, TimeUnit unit) throws InterruptedException;

向队列中获取元素,如果队列为空,那么阻塞当前线程,如果到达超时时间获取到元素,那么就将元素返回,否则返回null。

五:源码分析

juc中提供了许多BlockingQueue的实现,例如ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronousQueue等。今天的源码分析部分就以ArrayBlockingQueue为例,对阻塞队列的原理进行分析。

ArrayBlockingQueue是基于数组的结构来实现的一个阻塞队列。

主要的成员变量:

/** The queued items */
    final Object[] items;

    //下次获取元素的索引
    int takeIndex;

    /** items index for next put, offer, or add */
    //下次添加元素的索引
    int putIndex;

    //当前队列的元素个数
    int count;

    //ReentrantLock锁
    final ReentrantLock lock;

    //队列是否为空的condition 为空是await()
    private final Condition notEmpty;

    //队列是否满了的condition 满了的时候await()
    private final Condition notFull;

接下来我们主要对add(),put(),offer(),take()和poll()这几个方法进行分析。

offer(E e)方法

offer(E e)方法不支持添加null元素,添加null直接抛出异常,如果队列已经达到了最大容量,那么返回false,否则调用enqueue()方法加入到队列中,并且返回true。

public boolean offer(E e) {
        //检查元素是否为null 为null抛出异常
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //加锁 保证线程安全
        lock.lock();
        try {
            //已经达到最大容量 返回false
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                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;
        //putIndex(下次存在元素的索引下标)+1,如果putIndex和数组长度一样,那么需要重置成0
        if (++putIndex == items.length)
            putIndex = 0;
        //元素个数+1
        count++;
        //通过condition的signal()方法唤醒正在等待获取元素的线程
        notEmpty.signal();
    }

offer(E e, long timeout, TimeUnit unit)

public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        //检查添加的元素是否为null 为null的话抛出异常
        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        try {
            //如果队列已经满了 那么调用condition的awaitNanos()方法阻塞线程指定时长
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                //awaitNanos()返回的nanos小于等于0  代表已经超时 超时的话直接返回false    
                nanos = notFull.awaitNanos(nanos);
            }
            //没有超时被唤醒 说明已经有线程从队列中取了元素,队列不是满的了 所以可以向队列中添加元素了。
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

add(E e)

add()方法会调用offer()方法添加元素 ,成功返回true 否则抛出异常。

public boolean add(E e) {
        //调用offer()方法添加元素 ,成功返回true 否则抛出异常
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

put(E e)

put()方法向队列中添加元素。如果队列满了,那么对调用condition的await()方法阻塞当前线程,直到其他线程调用signal()方法唤醒,如果队列不是满的,那么跳出while循环,调用enqueue()方法添加元素。

public void put(E e) throws InterruptedException {
        //检查元素是否为null
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        try {
            //如果队列满了 就等待
            while (count == items.length)
                notFull.await();
            //直到队列不是满的 才会调用enqueue()方法添加元素
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

take() take()方法获取元素,如果队列为空,那么调用condition的await()方法等待,直到被其他线程唤醒,队列不为空的情况下跳出while循环,然后调用dequeue()删除元素。

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        try {
            while (count == 0)
            //队列为空 那么进行等待
                notEmpty.await();
            //直到队列不为空 调用dequeue()方法获取元素,并且删除元素
            return dequeue();
        } 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];
        //然后将数组下标位置的值设置为null,
        items[takeIndex] = null;
        //takeIndex(下次获取元素的数组下标) +1,如果takeIndex已经等于数组的长度,那么需要重置为0
        if (++takeIndex == items.length)
            takeIndex = 0;
        //元素个数减少
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

poll(long timeout, TimeUnit unit)

pull()方法获取元素的时候,如果对了为空,那么会阻塞当前线程指定的时间,如果在超时时间内获取到了元素,就直接返回,否则返回null。

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)
                    //超时的话返回null
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            //没有超时,调用dequeue()方法获取元素并且删除元素
            return dequeue()方法获取元素并且删除元素;
        } finally {
            lock.unlock();
        }
    }

六:总结

阻塞队列是基于ReentrantLock和condition来实现的,如果你了解ReentrantLock和condition的原理,那么阻塞队列这一块是非常容易理解的,想要了解condition原理可以看我的另外一篇文章。文章地址:condition实现原理