阻塞队列

常见的阻塞队列

ArrayBlockingQueue

手写一个阻塞队列_缓存

  1. ArrayBlockingQueue 是一个有界队列,其内部的实现是基于数组来实现的,我们在创建时需要指定其长度,它的线程安全性由 ReentrantLock 来实现的。
  2. 和 ReentrantLock 一样,如果 ArrayBlockingQueue 被设置为非公平的,那么就存在插队的可能;如果设置为公平的,那么等待了最长时间的线程会被优先处理,其他线程不允许插队,不过这样的公平策略同时会带来一定的性能损耗,因为非公平的吞吐量通常会高于公平的情况。

LinkedBlockingQueue

手写一个阻塞队列_阻塞队列_02

  1. 它是一个由链表实现的队列,这个队列的长度是 Integer.MAX_VALUE ,对此我们可以认为这个队列基本属于一个无界队列(也又认为是有界队列)。此队列按照先进先出的顺序进行排序。

SynchronousQueue

  1. synchronousQueue 是一个不存储任何元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。同时它也支持公平锁和非公平锁。
  2. SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。

使用SynchronousQueue的目的就是保证“对于提交的任务,如果有空闲线程,则使用空闲线程来处理;否则新建一个线程来处理任务”。

PriorityBlockingQueue

手写一个阻塞队列_缓存_03

  1. PriorityBlockingQueue 是一个支持优先级排序的无界阻塞队列,可以通过自定义实现 compareTo() 方法来指定元素的排序规则,或者通过构造器参数 Comparator 来指定排序规则。
  2. 它的 take 方法在队列为空的时候会阻塞,但是正因为它是无界队列,而且会自动扩容,所以它的队列永远不会满,所以它的 put 方法永远不会阻塞,添加操作始终都会成功

DelayQueue

  1. DelayQueue是一个无界的优先级BlockingQueue,用于放置实现了Delayed接口(该接口需要实现compareTo和getDelay方法)的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。
  2. DelayQueue 应用场景:1. 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。2. 定时任务调度。

基于LinkedBlockingDeque 实现一个简易的BlockingDeque

简易BlockingDeque的功能:实现基本的take和put操作。

public class BlockQueueDemo {

public static void main(String[] args) throws InterruptedException {
BlockQueue<Integer> queue = new BlockQueue<Integer>(4);
queue.put(5);

new Thread(() -> {
try {
System.out.println("------添加------");
queue.put(11);
queue.put(12);
queue.put(13);
queue.put(14);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("------取出------");
queue.take();
Thread.sleep(1);
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

}

static final class Node<E> {
E item;
Node<E> next;
Node(E x) {
item = x;
}
}

static class BlockQueue<E> {

private int capacity;
private int count;
private Node<E> first;
private Node<E> last;

final private Lock lock = new ReentrantLock();
Condition full = lock.newCondition();
Condition empty = lock.newCondition();

public BlockQueue(int capacity) {
this.capacity = capacity;
first= (Node<E>) new Node<Integer>(-1);
}


public void put(E element) throws InterruptedException {
lock.lock();
Node<E> node = new Node<E>(element);
try {
while (!linkLast(node))
full .await();
} finally {
lock.unlock();
}
}

private boolean linkLast(Node node) {
if (count >= capacity)
return false;
if (last==null){
last=node;
first.next=last;
}

else {
last.next=node;
last = node;
}

++count;
System.out.println("添加"+node.item);
empty.signal();
return true;
}

/**
* 阻塞出队
*/
public E take() throws InterruptedException {
lock.lock();
try {
E x;
while ( (x = unlinkFirst()) == null)
empty.await();
return x;
} finally {
lock.unlock();
}
}
private E unlinkFirst() {
Node<E> f = first.next;
if (f == null)
return null;

E item = f.item;
first.next=first.next.next;
--count;
full.signal();
System.out.println("取出"+item);
return item;
}
}
}

解析:

  1. 选用的是单链表,其实LinkedBlockingDeque源码使用的是双链表
  2. take和put均采用阻塞的方式。