前言
为什么要使用线程池
1.在Java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。
2.除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会是系统由于过度消耗内存或“切换过度”而导致系统资源不足。
3.为了防止资源不足,服务器应用程序需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁的线程的次数,特别是一个些资源耗费比较大但是线程的创建和销毁尽量利用已有对象来进行服务,这就是“池化资源”技术产生的原因。
线程池主要解决的问题
线程生命周期开销问题和资源不足问题(通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上来,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟,这样,就可以立即为请求服务,使应用程响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况)
线程池的组成部分:
一个比较简单的线程池至少应该包含线程池管理器、工作线程、任务队列、任务接口等部分。
作用:
线程池管理器 (ThreadPool):创建、销毁并管理线程池,将工作线程放入线程池中
工作线程 (PoolWorker):一个可以循环执行任务的线程,在没有任务时进行等待
任务队列 (taskQueue):提供一种缓冲机制,将没有处理的任务放在任务队列中
任务接口 (Task):每个任务必须实现的接口,主要用来规定任务的入口,任务执行完后的收尾工作,任务的执行装填等,工作线程工通过该接口调度任务的执行。
理解ThreadPoolExecutor中execute()方法原理
首先普及一下什么使ctl
ctl位操作变量
ThreadPoolExecutor有一个AtomicInteger变量,叫ctl(control的简写),一共32位,
高3位为线程池的状态runstatus(5中:Running,Shutdown,Stop,Tidying,Terminate),
低29位存当前有效线程数workerCount
分析execute()方法的原理
下面是execute的源码
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//判断工作线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//执行addworker,创建一个核心线程,创建失败重新获取ctl
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果工作线程数大于核心线程数,判断线程池的状态是否为running,并且可以添加进队列
//如果线程池不是running状态,则执行拒绝策略,(还是会调用一次addworker)
if (isRunning(c) && workQueue.offer(command)) {
//再次获取ctl,进行双重检索
int recheck = ctl.get();
//如果线程池是不是处于RUNNING的状态,那么就会将任务从队列中移除,
//如果移除失败,则会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程
//如果移除成功,就执行拒绝策略,因为线程池已经不可用了;
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//线程池挂了或者大于最大线程数
else if (!addWorker(command, false))
reject(command);
}