【线程池】常用的三种阻塞队列
简介
我们在了解完线程池的参数配置和常用线程池后发现,每种线程池会根据不同的需求去选择不同的队列来存储线程任务。线程池的对应队列如下:
可以看到,五大常用的线程池,会用到三种线程池
LinkedBlockingQueue
LinkedBlockingQueue是一种没有容量上限的队列,也就是说,用了这个队列的线程池,就可以没有上限的去保存队列任务。这种需求场景就很符合FixedThreadPool和SingleThreadExecutor,这两种线程池都有一个相同点,那就是核心线程数和最大线程数是一致的,线程数都固定了,当任务多的时候线程处理不过来的线程就会放到队列中,这时就需要一个没有上限的队列来存储线程池的任务了。
SynchronousQueue
SynchronousQueue内部结构中没有存储队列的容器,因此没有办法去存储队列消息,那么就会有人想,不能存储消息那算哪门子队列是吧,其实队列不一定需要会存储消息,SynchronousQueue虽然不能存储队列消息,但是他可以阻塞队列消息,可以很好的完成中转消息的用处,接下来我们可以参考下面这段代码
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Thread producer = new Thread(()->{
int i = 0;
while (true){
System.out.println("生产者开始生产消息...");
String msg = "这是第"+(++i)+"条消息";
try {
int seconds = getRandomSeconds();
System.out.println("生产者生产消息花费了"+seconds+"秒");
TimeUnit.SECONDS.sleep(seconds);
queue.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产者消息生产完毕...");
}
});
Thread consumer = new Thread(()->{
while (true){
System.out.println("消费者开始消费消息...");
try {
System.out.println("消费者消费到消息:"+queue.take());
int seconds = getRandomSeconds();
System.out.println("消费者消费消息花费了"+seconds+"秒");
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
private static int getRandomSeconds(){
Random random = new Random();
return random.nextInt(10)+1;
}
}
生产者会随机1到10秒去生产消息,消费者也会随机1到10秒去消费消息,如果当遇到生产者正在生产消息的时候,消费者端就会因为没有消息消费而阻塞在那里,同理,如果消费者正在消费消息,生产者也会因为队列没法存储消息而阻塞在那里。
像这种场景就很适合CachedThreadPool这样的线程池,因为CachedThreadPool可以创建的线程数是无限的,也就是说在这个线程池里面,任务队列能不能存储消息其实已经变的可有可无了,但是为了提高线程队列中转消息的性能,SynchronousQueue就变的更加合适,因为他想对于LinkedBlockingQueue,SynchronousQueue少了去存储消息的性能消耗,自然的性能就增加了。
DelayedWorkQueue
DelayedWorkQueue的数据结构是采用数组来实现堆,并且内部元素并不是按照放入的时间顺序来排序的,而是会按照延迟的时间长短对任务进行排序。我们可以看到ScheduledThreadPool和SingleThreadScheduledExecutor都适用DelayedWorkQueue来存放队列,其实就是因为他可以按延迟时间长短排序任务执行的特性,来实现线程池定时的功能。