“这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战
玩转Java线程池:
- 玩转Java线程池一:ThreadPoolExecutor的执行流程和原理
- 玩转java线程池二:ThreadPoolExecutor的使用
- 玩转Java线程池三:线程池设计的顶层接口Executor解析
一、为什么要用线程池
在现实开发中是不会手动地去new一个线程出来用的,阿里巴巴的Java开发手册中也写到不要手动创建线程,要使用线程池。几乎所有的需要异步或者并发执行的程序都可以使用线程池。使用线程池有三个好处:
- 降低资源的消耗:在线程池中有一些已经创建好的线程,通过重复利用这些线程,可以减低线程的创建和销毁造成的消耗。
- 提高响应速度:当要执行的任务达到之后,不需要去创建线程池,可以立即执行。
- 提高线程的可管理性:线程是珍贵的资源,要是重量级的。不能无限制地去创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一管理、调优和监控线程。
二、线程池的处理流程
当提交一个新任务到线程池时,线程池的处理流程如下:
1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。 3、线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
如下图:
Java中线程池的类是ThreadPoolExecutor,里面执行的方法是execute方法,主要的execute的执行流程如下:
总得来说,ThreadPoolExecutor的execute方法大致的设计思路是这样的:
1、如果当前线程池运行的线程少于corePoolSize,那么就创建一个线程来执行提交过来的任务。(此操作需要获取全局锁)
2、如果当前线程池运行的线程等于或者大于corePoolSize,那么提交过来任务会被放置在BlockingQueue(阻塞队列)中。
3、如果BlockingQueue(阻塞队列)已满,那么就创建新的线程来处任务(此操作需要获取全局锁)
4、如果撞见新的线程之后,线程池中的线程数大于maximumPoolSize,提交过来的任务会被拒绝执行,并执行拒绝策略
三、execute方法源码解析
public void execute(Runnable command) {
// 如果当前任务为空,抛出空指针异常
if (command == null)
throw new NullPointerException();
// 获取当前线程池的状态+线程个数变量的组合值
int c = ctl.get();
// 如果当前线程数小于核心线程池大小,那么就创建线程并执行当前任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果线程池处于RUNNING状态,把任务添加到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
// 二次检查
int recheck = ctl.get();
// 如果当前线程池状态不是RUNNING则从队列中删除任务,并且执行线程池的拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果当前线程池为空,则创建一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果队列满了,则新增线程,新增线程失败则执行线程池的拒绝策略
else if (!addWorker(command, false))
reject(command);
}