并发编程
Executor线程池原理和源码解析
线程
线程是调度CPU资源的最小单位,线程模型分为KLT模型和ULT模型,JVM使用的是KLT线程模型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里面有一个对应的线程。
java线程的状态
NEW 新建
RUNNABLE 运行
BLOCKED 阻塞
WAITING 无限等待(需要手动唤醒)
TIMED_WAITING 计时等待
状态切换如下
线程池
“线程池”,顾名思义就是一个线程缓存,线程是稀缺资源,如果被无线创建,不仅会消耗系统资源,还会降低系统的稳定性,因此java中提供线程池对线程进行统一分配,调优和监控
线程池介绍
在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理。如果每次请求都创建一个新的线程的话,实现起来非常方便。但是存在一个问题:
如果并发的数量非常多,但每个线程的执行时间很短,这样就频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的资源要比处理实际的用户请求的时间和资源更多。
那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
这就是线程池的目的了。线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
什么时候使用线程池?
- 单个任务处理时间比较短
- 需要处理的任务数量很大
线程池优势
- 重用存在的线程,减少线程创建,消亡的开销,提高性能
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程的实现方式
1.继承Thread类,重写run方法
2.实现Runnable接口,实现run方法
// 实现Runnable接口的类将被Thread执行,表示一个基本的任务
public interface Runnable {
// run方法就是它所有的内容,就是实际执行的任务
public abstract void run();
}
3.实现Callable接口,实现call方法
//Callable同样是任务,与Runnable接口的区别在于它接收泛型,同时它执行
public interface Callable<V> {
// 相对于run方法的带有返回值的call方法
V call() throws Exception;
}
Executor框架
Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。
下面是他的继承和实现
线程池重要方法
Executor有一个重要的子接口ExecutorService,定义了线程池的具体行为
// 执行Runnable类型的任务(继承自executor)
void execute(Runnable command);
// 终止线程池,不接受新的任务,但是已经提交的任务会执行完成
void shutdown();
// 可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
// 立刻终止线程池,不再接受新任务,当前执行的任务也不执行
List<Runnable> shutdownNow();
// 判断是否所有任务都执行完毕了。
boolean isTerminated();
// 判断线程池是否被终止
boolean isShutdown();
ThreadPoolExecutor线程池重点属性
// ctl是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,
//它包含两个部分的信息:线程池的运行状态(runState)和线程池内有效线程的数量(workerCount),
//这里可以看到,使用了integer类型来保存,高3位保存runState,低29位保存workerCount.
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS就是29
private static final int COUNT_BITS = Integer.SIZE - 3;
// CAPACITY就是1左移29位减1(29个1),这个常
// 量表示workerCount的上限值,大约是5亿。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
线程池的五种状态
private static final int RUNNING = -1 << COUNT_BITS; //高3位为111
private static final int SHUTDOWN = 0 << COUNT_BITS; //高3位为000
private static final int STOP = 1 << COUNT_BITS; //高3位为001
private static final int TIDYING = 2 << COUNT_BITS; //高3位为010
private static final int TERMINATED = 3 << COUNT_BITS; //高3位为011
状态说明
1.RUNNING
能够接收新任务,以及对已添加的任务进行处理,线程一旦被创建,就处于RUNNING状态,并且线程 池中的任务数位0
2.SHUTDOWN
不接受新任务,但是能处理已经添加的任务,调用shutdown方法,线程池由RUNNING切换为SHUTDOWN
3.STOP
不接受新任务,不处理已添加的任务,并且会中断正在处理的任务。调用线程池的shutdownNow方法,线程池由RUNNING 或者SHUTDOWN切换为STOP
4.TIDYING
当所有任务已终止,ctl记录的“任务数量”为0,线程池变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
5.TERMINATED
线程池彻底终止,就会变成TERMINATED状态。线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
进入TERMINATED的条件如下:
- 线程池不是RUNNING状态;
- 线程池状态不是TIDYING状态或TERMINATED状态;
- 如果线程池状态是SHUTDOWN并且workerQueue为空;
- workerCount为0;
- 设置TIDYING状态成功。
这一节我们讲了线程池的基类Executor的原理以及通过源码解析,讲解Executor的基本属性和方法,下一节我们来讲讲Executor的具体实现ThreadPoolExecutor