阻塞队列
简介
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。
BlockQueue的核心方法:
offer(anObject)offer(E o, long timeout, TimeUnit unit)put(anObject)poll(time)poll(long timeoout, TimeUnit unit)take()drainTo()
Java中的阻塞队列
| 队列 | 解释 |
|---|---|
| ArrayBlockingQueue | 由数组结构组成的有界阻塞队列 |
| LinkedBlockingQueue | 由链表结构组成的有界阻塞队列 |
| PriorityBlockingQueue | 支持优先级排序的无界阻塞队列 |
| DelayQueue | 使用优先级队列实现的无界阻塞队列 |
| SynchronousQueue | 不存储元素的阻塞队列 |
| LinkedTransferQueue | 由链表结构组成的无界阻塞队列 |
| LinkedBlockingDeque | 由链表结构组成的双向阻塞队列 |
阻塞队列的实现原理
以ArrayBlockingQueue为例,我们先来看看代码,如下所示:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
/** 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;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
}
接下来我们来看一下put方法:
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
//获取锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果元素已满则进行等待
while (count == items.length)
notFull.await();
//当线程被其它线程唤醒则插入元素
enqueue(e);
} finally {
//解锁
lock.unlock();
}
}
接下来看看enqueue(e)方法:
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唤醒正在等待取元素的线程
notEmpty.signal();
}
再来看看take方法:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队列为空则进行等待
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
跟put方法实现类似,put方法等待的是notFull信号,而take方法等待的是notEmpty信号。 下面是dequeue方法的实现:
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;
}
阻塞队列的使用场景
阻塞队列的一个常见的使用场景就是生产者-消费者模式,首先不使用阻塞队列实现,代码如下:
/*
使用阻塞队列实现生产者消费模式
*/
public class customerToProducer {
private int queueSize = 3;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
public static void main(String[] args) throws InterruptedException {
customerToProducer cp = new customerToProducer();
Producer producer = cp.new Producer();
Consumer consumer = cp.new Consumer();
producer.start();
consumer.start();
}
//消费者类
class Consumer extends Thread{
@Override
public void run() {
while(true){
synchronized(queue){
while(queue.size()==0){
try{
System.out.println("队列空,等待数据");
queue.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
//每次移走队列元素
queue.poll();
queue.notify();
System.out.println("移走元素成功!");
System.out.println("剩余:"+queue.size());
}
}
}
}
//生产者类
class Producer extends Thread{
@Override
public void run() {
while(true){
synchronized(queue){
while(queue.size()==queueSize){
try{
System.out.println("队列满,等待空余空间");
queue.wait();
}catch(InterruptedException e){
e.printStackTrace();
queue.notify();
}
}
//每次插入一个元素
queue.offer(1);
queue.notify();
System.out.println("插入元素成功!");
System.out.println("剩余:"+queue.size());
}
}
}
}
}
下面是使用阻塞队列实现的生产者-消费者模式:
public class customerToProducer {
private int queueSize = 10;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(queueSize);
public static void main(String[] args) throws InterruptedException {
customerToProducer1 cp = new customerToProducer();
Producer producer = cp.new Producer();
Consumer consumer = cp.new Consumer();
producer.start();
consumer.start();
}
//消费者类
class Consumer extends Thread{
@Override
public void run() {
while(true){
try{
queue.take();
System.out.println("移走元素成功!");
System.out.println("剩余:"+queue.size());
this.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
//生产者类
class Producer extends Thread{
@Override
public void run() {
while(true){
try{
queue.put(1);
System.out.println("插入元素成功!");
System.out.println("剩余:"+queue.size());
this.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
很显然,使用阻塞队列实现无须单独考虑同步和线程间通信的问题,其实现起来很简单。
线程池
在编程中经常会使用线程来异步处理任务,但是每个线程的创建和销毁都需要一定的开销。如果每次执行一个任务都需要开一个新线程去执行,则这些线程的创建和销毁将消耗大量的资源;并且线程都是“各自为政”的,很难对其进行控制,更何况有一堆的线程在执行。这时就需要线程池来对线程进行管理。
ThreadPoolExecutor
可以通过ThreadPoolExecutor来创建一个线程池,ThreadPoolExecutor类一共有4个构造方法。其中,拥有最多参数的构造方法如下所示:
public ThreadPoolExecutor(
//核心线程数
int corePoolSize,
//线程池允许创建的最大线程数
int maximumPoolSize,
//非核心线程闲置的超时时间
long keepAliveTime,
//keepAliveTime参数的时间单位
TimeUnit unit,
//任务队列
BlockingQueue<Runnable> workQueue,
//线程工厂
ThreadFactory threadFactory,
//饱和策略
RejectedExecutionHandler handler) {
...
}
线程池的处理流程和原理
上图介绍了线程池的处理流程,但还不是很直观。结合下图,我们就能更好地了解线程池的原理:
线程池的种类
FixedThreadPool
FixedThreadPool 是可重用固定线程数的线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool只有核心线程,并且数量是固定的,没有非核心线程。
CachedThreadPool
CachedThreadPool是一个根据需要创建线程的线程池:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool没有核心线程,非核心线程是无界的。
SingleThreadExecutor
SingleThreadExecutor是使用单个工作线程的线程池:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor只有一个核心线程。
ScheduledThreadPool
ScheduledThreadPool是一个能实现定时和周期性任务的线程池:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
这里创建了ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,它主要用于给定延时之后的运行任务或者定期处理任务。ScheduledThreadPoolExecutor 的构造方法如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor的execute方法的执行示意图如下:
DelayedWorkQueue会将任务进行排序,先要执行的任务放在队列的前面。
当执行完任务后,会将ScheduledFutureTask中的time变量改为下次要执行的时间并放回到DelayedWorkQueue中。