突击并发编程JUC系列演示代码地址:https://github.com/mtcarpenter/JavaTutorial
Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。
-
降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -
提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行。 -
提高线程的可管理性。 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。
线程池的实现原理
当提交一个新任务到线程池时,线程池的处理流程如下:
-
线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。 -
线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。 -
线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
ThreadPoolExecutor
执行execute()
方法的示意图 如下:
ThreadPoolExecutor
执行execute
方法分下面 4 种情况:
-
1、如果当前运行的线程少于 corePoolSize
,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。 -
2、如果运行的线程等于或多于 corePoolSize
,则将任务加入BlockingQueue
。 -
3、如果无法将任务加入 BlockingQueue
(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。 -
4、如果创建新线程将使当前运行的线程超出 maximumPoolSize
,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
方法。
ThreadPoolExecutor
采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor
完成预热之后(当前运行的线程数大于等于corePoolSize
),几乎所有的execute()
方法调用都是执行步骤 2,而步骤2不需要获取全局锁。
线程池创建的方式
-
newSingleThreadExecutor()
: 他的特点是在于线程数目被限制位1:操作一个无界的工作队列,所以它保证了所有的任务的都是顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。 -
newCachedThreadPool()
:它是一种用来处理大量短时间工作任务的线程,具有几个鲜明的特点,它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程,如果线程闲置的时间超过 60 秒,则被终止并移除缓存;长时间闲置时,这种线程池不会消耗什么资源,其内部使用synchronousQueue
作为工作队列。 -
newFixedThreadPool(int nThreads)
:重用指定数目 nThreads 的线程,其背后使用的无界的工作队列,任何时候最后有 nThreads 个工作线程活动的,这意味着 如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现,如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads。 -
newSingleThreadScheduledExecutor()
: 创建单线程池,返回ScheduleExecutorService
可以进行定时或周期性的工作强度。 -
newScheduleThreadPool(int corePoolSize)
: 和newSingleThreadSceduleExecutor()
类似,创建的ScheduledExecutorService
可以进行定时或周期的工作调度,区别在于单一工作线程还是工作线程。 -
newWorkStrealingPool(int parallelism)
:这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool
利用work-strealing
算法 并行的处理任务,不保证处理顺序。 -
ThreadPollExecutor
:是最原始的线程池创建,上面 1-3 创建方式 都是对ThreadPoolExecutor
的封装。
上面 7 种创建方式中,前 6 种 通过Executors
工厂方法创建,ThreadPoolExecutor
手动创建。
ThreadPollExecutor 构造方法
下面介绍下 ThreadPoolExecutor
接收 7 个参数的构造方法
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略
)
-
corePoolSize
: 核心线程数线程数定义了最小可以同时运行的线程数量。 -
maximumPoolSize
: 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -
workQueue
: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 -
keepAliveTime
:线程活动保持时间,当线程池中的线程数量大于corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime
才会被回收销毁。 -
unit
:keepAliveTime
参数的时间单位。 -
threadFactory
: 任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。 -
ArrayBlockingQueue
:是一个基于数组结构的有界阻塞队列,此队列按FIFO
(先进先出)原则对元素进行排序。 -
LinkedBlockingQueue
:一个基于链表结构的阻塞队列,此队列按FIFO
排序元素,吞吐量通常要高于ArrayBlockingQueue
。静态工厂方法Executors.newFixedThreadPool()
使用了这个队列。 -
SynchronousQueue
:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue
,静态工厂方法Executors.newCachedThreadPool
使用了这个队列。 -
PriorityBlockingQueue
:一个具有优先级的无限阻塞队列。 -
handler
:饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy
,表示无法处理新任务时抛出异常。在JDK 1.5
中 Java 线程池框架提供了以下4种策略。 -
AbortPolicy
:直接抛出异常。 -
CallerRunsPolicy
:只用调用者所在线程来运行任务。 -
DiscardOldestPolicy
:丢弃队列里最近的一个任务,并执行当前任务。 -
DiscardPolicy
:不处理,丢弃掉
欢迎关注公众号 山间木匠 , 我是小春哥,从事 Java 后端开发,会一点前端、通过持续输出系列技术文章以文会友,如果本文能为您提供帮助,欢迎大家关注、在看、 点赞、分享支持,我们下期再见!
原文始发于微信公众号(山间木匠):突击并发编程JUC系列-ThreadPoolExecutor 线程池
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/22710.html