java JUC 并发包之阻塞队列——BlockQueue

在学习完java的同步队列、Lock和等待通知机制之后,再来看阻塞队列会觉得阻塞队列更加容易理解。阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入:当队列满时,队列会阻塞插入元素的线程,直到队列不满;阻塞移除:在队列为空时,获取元素的线程会等待队列变为非空。在阻塞队列不可用时,这两个附加操作提供了4种处理方式

方法/处理方式

抛出异常

返回特殊值

一直阻塞

超时退出

插入方法

add(e)

offer(e)

put(e)

offer(e, time, unit)

移除方法

remove()

poll()

take()

poll(time,unit)

检查方法

element()

peek()

-

-

·抛出异常是指当队列满时,如果再往队列里插入元素,会抛出IllegalStateException("Queue full")异常。当队列空时,从队列里获取元素会抛出NoSuchElementException异常。

返回特殊值是指当往队列插入元素时,会返回元素是否插入成功,成功返回true。如果是移 除方法,则是从队列里取出一个元素,如果没有则返回null。

·一直阻塞是指当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者 线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队 列会阻塞住消费者线程,直到队列不为空。后面我们会介绍这种模式的源码。
·超时退出是指当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程 一段时间,如果超过了指定的时间,生产者线程就会退出。

jdk为我们提供了一下几种阻塞队列,他们的实现方式几乎是相同的,我们后面以一种方式讲述阻塞队列的实现,如下表为jdk为我们提供的几种阻塞队列与描述:

阻塞队列 描述
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列
PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
DelayQueue 一个使用优先级队列实现的无界阻塞队列
SynchronousQueue 一个不存储元素的阻塞队列
LinkedTransferQueue 一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列

如上,为jdk为我们提供的阻塞队列,如果我们查看它们的源码,发现它们的变量定义中都有Lock接口和Condition接口作为字段,也就是说,他们的实现是以Lock和Condition作为基础的。只要我们掌握了Lock和Condition的使用,就很容易掌握阻塞队列的使用。我们以LinkedBlockingQueue为例,定义代码如下:

/** Lock held by take, poll, etc */ take、poll操作时获取锁
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */等待take操作
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */ put offer时获取锁
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */ //等待put操作
private final Condition notFull = putLock.newCondition();

接下来我们要看,LinkedBlockingQueue是如何存储元素和取出元素的,首先我们以存储元素为例,我们以put方法为例,代码如下所示:

//唤醒一个take等操作的线程
private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
}
 public void put(E e) throws InterruptedException {
        //如果传值为null,抛出异常
        if (e == null) throw new NullPointerException();
        int c = -1;
        //创建节点
        Node<E> node = new Node<E>(e);
        //获取putLock实例
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        //获取锁,可中断
        putLock.lockInterruptibly();
        try {
            //如果容量已满
            while (count.get() == capacity) {
                //存放元素的线程等待
                notFull.await();
            }
            //存入队列
            enqueue(node);
            c = count.getAndIncrement();
            //如果队列不满,唤醒存入队列的线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            //释放锁
            putLock.unlock();
        }
        //如果为0
        if (c == 0)
            //唤醒一个取出元素的线程
            signalNotEmpty();
    }

上面我们介绍了存放元素的源码,其他几种存放元素的逻辑与上面的类似,这里不作介绍,下面我们分析获取元素的源码:take方法,源码如下:

//唤醒一个put操作的线程
 private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
} 
public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //获取take锁
        takeLock.lockInterruptibly();
        try {
            //如果队列元素为空,获取线程等待
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            //如果队列元素不为空,唤醒取元素线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            //释放锁
            takeLock.unlock();
        }
        //如果c=capacity 唤醒put操作的线程
        if (c == capacity)
            signalNotFull();
        return x;
    }

上面我们介绍了LinkedBlockingQueue的put和take操作,可以知道阻塞队列的主要原理就是等待通知机制,使用了并发发包提供的Lock、Condition、LockSupport组件。只要我们熟悉了这三个组件的的使用就能容易实现自己的阻塞队列。如果想要知道其他几种阻塞队列的具体实现可以自行参阅源码,其核心就是等待通知机制以及上面所说的三个组件。

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108456825
今日推荐