阻塞队列模型和线程池

简介:

阻塞队列模型介绍

阻塞队列模型和线程池息息相关,因此本篇博客先介绍阻塞队列的相关知识。如下图所示:

wKiom1b2RQ2SNHWrAAAu0KDMMdQ915.png

首先我们来说,什么是Queue,然后在谈什么是BlockingQueue。

那么什么是Queue呢?一句话,就是一端进,另一端出,这样就形成了First In , First Out,即先进先出。而BlockingQueue只不过是在Queue的基础上进行了2个附加操作而已:如果Queue空,那么Out线程阻塞,如果Queue满,那么In线程阻塞。


理解了上面的Queue/BlockingQueue,那么就好理解Deque/BlockingDeque了。

Deque,就是双端队列,其实就是说在2端,都可以进行IN/OUT,当然如果我们只在同一端进行IN/OUT,那么自然形成了栈结构(先进后出)。


其次,我们先来看看java.util.concurrent.BlockingQueue的API列表:

wKiom1b2RsSBMEufAAAd4AxQv2M881.png

其实从这里可以看出JAVA API的一个思路,往往一个操作提供多种选择:

如果队列是空的,那么消费线程该如何处理呢?

可以立刻抛出异常,也可以返回false/NULL,也可以过一段时间在TRY...


好了,到这里,我们只看到了接口API,如果要你来实现,你会怎么做呢?又会有哪些疑问呢?


思考:

是否应该提供一个存储,来放置队列中的元素呢?

这个存储应该多大呢?可以是什么形式呢?

队列中的元素是否存在优先级排序呢?

对队列中的元素进行操作时,是否应该有锁的控制呢?

一端IN,另一端OUT,这2个操作可以同时进行吗?


带着这些疑问,我们来对典型的BlockingQueue来进行分析。


ArrayBlockingQueue PK LinkedBlockingQueue


存储PK:

从名称上就可以知道,一个用的是数组,另一个用的是链表。看看源码验证下:


ArrayBlockingQueue:

wKiom1b2TBqykUFqAAAHWCwuOfk038.png


LinkedBlockingQueue:

wKiom1b2TDGTfNZ7AAArcPBhXaU329.png


容量PK:

ArrayBlockingQueue可以通过构造方法指定容量:

wKioL1b2TWqAkAZEAAAilXAs8XU931.png

而LinkedBlockingQueue如果在初始化时不指定容量,那么将是Integer.MAX_VALUE,这一点很重要,特别是在生产者的速度大于消费者的速度,由于此时无容量限制,将导致队列中的元素开始膨胀,那么将消耗掉大量系统资源。

wKioL1b2TiPjk5wWAABUl54WecY009.png


锁PK:

在ArrayBlockingQueue中只有一个锁:

wKiom1b2TdKAEXG_AAAJ8rP7w4w494.png

而在LinkedBlockingQueue中有2个锁:

wKioL1b2TqzhaX9HAAANfu9FdwE655.png

wKiom1b2TjeAQpuMAAAL2dVdzdM519.png

其实到这里,我们已经可以大致猜测出,LinkedBlockingQueue对于take/put使用了分别的锁,从而比ArrayBlockingQueue在高并发下更具优势。


我们再来看看其他BlockingQueue:

DelayQueue:延迟队列,实际上是说,队列中的元素生效的话,有个时间差。

PriorityQueue:优先级队列,会提供Comparator来进行队列中的元素的排序。

SynchronousQueue:这个队列比较特殊,因为没有存储机制,实际上只是做了一个生产者和消费者的传递机制。


线程池介绍

如果任务到达时,才开始创建线程,这实际上会让任务的执行被延迟,于是产生了线程池的概念,如果在池子中已经存在了一批线程,那么任务到达时自然省去了线程创建的时间,相当于提高了响应速度。其次,如果线程执行完任务后,在放入池子中,这相当于在复用线程,达到了资源节约的目的。当然,如果任务的执行时间是远远大于线程的创建/销毁时间,其实就无所谓了。



快速创建线程池:Executors

wKiom1b2Vsyg8igYAAAxDv6DG20760.png


Executors提供了一系列的快速创建线程池的方法,比如:

wKioL1b2V-TBFjcAAAAdWmjC9AM355.png



wKiom1b2V2XwcB4ZAAAiRV0Onrs008.png


wKioL1b2WCDwT9pPAAAc9lhRhko375.png


创建数量固定的/单个的/缓存的  线程池。

可以看到线程池的创建利用到了上面提及的BlockingQueue,队列中的元素就是任务Runnable。

方法返回的都是ExecutorService的实现类:ThreadPoolExecutor。



线程池的核心:ThreadPoolExecutor

我们直接来看看ThreadPoolExecutor的构造方法:

wKiom1b2WLOCpoqmAABhcZlS7Bk959.png

理解这些参数,对于理解线程池的原理有很大帮助:


corePoolSize:线程池的核心线程数量,是线程数目的一个稳定峰值。


maximumPoolSize:线程池的最大线程数量,如果corePoolSize依旧满足不了需要,那么可以让线程增长至maximumPoolSize,一旦需要下降,那么超出核心线程的那一部分线程资源将被回收。


workQueue:这个队列是待处理的任务队列。实际上,在ThreadPoolExecutor中除此之外还存在一个正在处理的工作队列workers

wKioL1b2e2yDfnD9AAAI_i_YhEc158.png

keepAliveTime:超过核心线程数,又小于最大线程数目的线程在空闲的情况下,多久回收。


threadFactory:线程工厂,实际上用的是默认的DefaultThreadFactory,通过代码发现仅仅是针对Thread做了些设置(比如线程组/线程名称/后台/优先级等设置),将Runnable挂到Thread上而已。


handler:如果已经达到了最大线程数目,那么对于任务只能开始拒绝了,这个就是拒绝处理的策略类。


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1755533,如需转载请自行联系原作者

相关文章
|
2月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
154 2
|
7月前
|
安全 Java 调度
Netty源码—3.Reactor线程模型二
本文主要介绍了NioEventLoop的执行总体框架、Reactor线程执行一次事件轮询、Reactor线程处理产生IO事件的Channel、Reactor线程处理任务队列之添加任务、Reactor线程处理任务队列之执行任务、NioEventLoop总结。
|
7月前
|
安全 Java
Netty源码—2.Reactor线程模型一
本文主要介绍了关于NioEventLoop的问题整理、理解Reactor线程模型主要分三部分、NioEventLoop的创建和NioEventLoop的启动。
|
9月前
|
缓存 NoSQL 中间件
Redis的线程模型
Redis采用单线程模型确保操作的原子性,每次只执行一个操作,避免并发冲突。它通过MULTI/EXEC事务机制、Lua脚本和复合指令(如MSET、GETSET等)保证多个操作要么全成功,要么全失败,确保数据一致性。Redis事务在EXEC前失败则不执行任何操作,EXEC后失败不影响其他操作。Pipeline虽高效但不具备原子性,适合非热点时段的数据调整。Redis 7引入Function功能,支持函数复用,简化复杂业务逻辑。总结来说,Redis的单线程模型简单高效,适用于高并发场景,但仍需合理选择指令执行方式以发挥其性能优势。
232 6
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
268 3
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
179 1
|
NoSQL Redis 数据库
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
本文解释了Redis为什么采用单线程模型,以及为什么Redis单线程模型的效率和速度依然可以非常高,主要原因包括Redis操作主要访问内存、核心操作简单、单线程避免了线程竞争开销,以及使用了IO多路复用机制epoll。
380 0
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
358 0
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
175 0

热门文章

最新文章