本文关键字:
线程 , 线程池 , 单线程 , 多线程 , 线程池的好处 , 线程回收 , 创建方式, 核心参数 , 底层机制 , 拒绝策略 , 参数设置 , 动态监控 , 线程隔离
线程和线程池相关的知识,是Java学习或者面试中一定会遇到的知识点,本篇我们会从线程和进程,并行与并发,单线程和多线程等,一直讲解到线程池,线程池的好处,创建方式,重要的核心参数,几个重要的方法,底层实现,拒绝策略,参数设置,动态调整,线程隔离等等。主要的大纲如下:
线程池的好处
线程池,使用了池化思想来管理线程,池化技术就是为了最大化效益,最小化用户风险,将资源统一放在一起管理的思想。这种思想在很多地方都有使用到,不仅仅是计算机,比如金融,企业管理,设备管理等。
为什么要线程池?如果在并发的场景,编码人员根据需求来创建线程池,可能会有以下的问题:
- 我们很难确定系统有多少线程在运行,如果使用就创建,不使用就销毁,那么创建和销毁线程的消耗也是比较大的
- 假设来了很多请求,可能是爬虫,疯狂创建线程,可能把系统资源耗尽。
实现线程池有什么好处呢?
- 降低资源消耗:池化技术可以重复利用已经创建的线程,降低线程创建和销毁的损耗。
- 提高响应速度:利用已经存在的线程进行处理,少去了创建线程的时间
- 管理线程可控:线程是稀缺资源,不能无限创建,线程池可以做到统一分配和监控
- 拓展其他功能:比如定时线程池,可以定时执行任务
其实池化技术,用在比较多地方,比如:
- 数据库连接池:数据库连接是稀缺资源,先创建好,提高响应速度,重复利用已有的连接
- 实例池:先创建好对象放到池子里面,循环利用,减少来回创建和销毁的消耗
线程池相关的类
下面是与线程池相关的类的继承关系:
Executor
Executor 是顶级接口,里面只有一个方法 execute(Runnable command) ,定义的是调度线程池来执行任务,它定义了线程池的基本规范,执行任务是它的天职。
ExecutorService
ExecutorService 继承了 Executor ,但是它仍然是一个接口,它多了一些方法:
- void shutdown() :关闭线程池,会等待任务执行完。
- List<Runnable> shutdownNow() :立刻关闭线程池,尝试停止所有正在积极执行的任务,停止等待任务的处理,并 返回一个正在等待执行的任务列表(还没有执行的) 。
- boolean isShutdown() :判断线程池是不是已经关闭,但是可能线程还在执行。
- boolean isTerminated() :在执行shutdown/shutdownNow之后,所有的任务已经完成,这个状态就是true。
- boolean awaitTermination(long timeout, TimeUnit unit) :执行shutdown之后,阻塞等到terminated状态,除非超时或者被打断。
- <T> Future<T> submit(Callable<T> task) : 提交一个有返回值的任务,并且返回该任务尚未有结果的Future,调用future.get()方法,可以返回任务完成的时候的结果。
- <T> Future<T> submit(Runnable task, T result) :提交一个任务,传入返回结果,这个result没有什么作用,只是指定类型和一个返回的结果。
- Future<?> submit(Runnable task) : 提交任务,返回Future
- <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) :批量执行tasks,获取Future的list,可以批量提交任务。
- <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) :批量提交任务,并指定超时时间
- <T> T invokeAny(Collection<? extends Callable<T>> tasks) : 阻塞,获取第一个完成任务的结果值,
- <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) :阻塞,获取第一个完成结果的值,指定超时时间
可能有同学对前面的 <T> Future<T> submit(Runnable task, T result) 有疑问,这个reuslt有什么作用?
其实它没有什么作用,只是持有它,任务完成后,还是调用 future.get() 返回这个结果,用 result new 了一个 ftask ,其内部其实是使用了Runnable的包装类 RunnableAdapter ,没有对result做特殊的处理,调用 call() 方法的时候,直接返回这个结果。(Executors 中具体的实现)
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
// 返回传入的结果
return result;
}
}
还有一个方法值得一提: invokeAny() : 在 ThreadPoolExecutor 中使用 ExecutorService 中的方法 invokeAny() 取得第一个完成的任务的结果,当第一个任务执行完成后,会调用 interrupt() 方法将其他任务中断。
注意, ExecutorService 是接口,里面都是定义,并没有涉及实现,而前面的讲解都是基于它的名字(规定的规范)以及它的普遍实现来说的。
可以看到 ExecutorService 定义的是线程池的一些操作,包括关闭,判断是否关闭,是否停止,提交任务,批量提交任务等等。
AbstractExecutorService
AbstractExecutorService 是一个抽象类,实现了 ExecutorService 接口,这是大部分线程池的基本实现,定时的线程池先不关注,主要的方法如下:
不仅实现了 submit , invokeAll , invokeAny 等方法,而且提供了一个 newTaskFor 方法用于构建 RunnableFuture 对象,那些能够获取到任务返回结果的对象都是通过 newTaskFor 来获取的。不展开里面所有的源码的介绍,仅以submit()方法为例:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 封装任务
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 执行任务
execute(ftask);
// 返回 RunnableFuture 对象
return ftask;
}
但是在 AbstractExecutorService 是没有对最最重要的方法进行实现的,也就是 execute() 方法。线程池具体是怎么执行的,这个不同的线程池可以有不同的实现,一般都是继承 AbstractExecutorService (定时任务有其他的接口),我们最最常用的就是 ThreadPoolExecutor 。
ThreadPoolExecutor
重点来了!!! ThreadPoolExecutor 一般就是我们平时常用到的线程池类,所谓创建线程池,如果不是定时线程池,就是使用它。
先看 ThreadPoolExecutor 的内部结构(属性):
public class ThreadPoolExecutor extends AbstractExecutorService {
// 状态控制,主要用来控制线程池的状态,是核心的遍历,使用的是原子类
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 用来表示线程数量的位数(使用的是位运算,一部分表示线程的数量,一部分表示线程池的状态)
// SIZE = 32 表示32位,那么COUNT_BITS就是29位
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池的容量,也就是27位表示的最大值
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 状态量,存储在高位,32位中的前3位
// 111(第一位是符号位,1表示负数),线程池运行中
private static final int RUNNING = -1 << COUNT_BITS;
// 000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001
private static final int STOP = 1 << COUNT_BITS;
// 010
private static final int TIDYING = 2 << COUNT_BITS;
// 011
private static final int TERMINATED = 3 << COUNT_BITS;