目录
- 前置知识
- 课程内容
- 一、线程池
- 1.基本介绍
- 2.Executor接口
- 3.线程池的重点属性
- ctl字段
- RUNNING字段
- SHUTDOWN字段
- 二、线程池的创建及参数解读
- 三、核心源码解读
- 学习总结
前置知识
Q1:终止一个线程的方法有哪些?
答:通常有4个方法。其中前2个是使用stop()和destroy()方法终止,但是这2个都不推荐使用,因为他们是暴力终止线程,所以不保证资源被正确释放,甚至导致数据不一致。后2个则是通过设置一个线程中断标记了,自定义的中断标记(保证内存可见性),或者使用线程自有的interupte()
方法。
Q2:为什么需要线程池?
答:首先线程池严格来说是一种池化技术,重在资源的重复利用。另外我们也知道,JAVA的线程实现技术是【内核线程1:1】的实现方案,这势必造成,JAVA在创建线程的时候,涉及到【用户态】【内核态】的切换,所以算是比较重型的操作。
试想一下,在web开发中,服务器需要接受并处理请求,如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。所以,我们需要一个可重复使用的线程池。线程池的优势在于:
- 重用存在的线程,减少线程创建,消亡的开销,提高性能
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
课程内容
一、线程池
1.基本介绍
线程池严格来说是一种池化技术,重在资源重复利用,它就是一种多线程存在形式。在多线编程中,创建和销毁线程一项开销较的操作,所以线程池通过预先创建一组线程,并将任务分配给这些线程来提供高效的线程管理。
线程池通常包括一个线程队列和一个任务队列。线程队列中保存着可供复用的线程,任务队列中保存着需要执行的任务。
从上面我们可以看到,线程池有着显著的优点:降低资源消耗、提高响应速度、方便管理;可以复用线程、控制最搭并发数、管理线程等。
2.Executor接口
Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。它的继承图如下:
从图中我们可以看到一个很重要的接口叫做Executors
,这也是我们后面学的线程池的一个核心接口。在其中定义了线程池的具体行为。接口定义如下:
execute(Runnable command); // 执行Ruannable类型的任务
submit(task); // 可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
shutdown(); // 表示要关闭线程池,不再接受新的任务,但是会把已经提交的任务先完成
shutdownNow(); // 停止所有正在执行的任务,不再接受新的任务,并且也不执行等待种的任务,只是将等待任务列表返回
isTerminated(); // 测试是否所有任务都执行完了(只有调用了shutdown或者shutdownNow这里才可能返回真,不然永远是false)
isShutdown(); // 测试是否该ExecutorService已被关闭
3.线程池的重点属性
线程池中的重点属性如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池的状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
ctl字段
AtomicInteger ctl
:ctl字段,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,它包含两部分信息:【线程池的运行状态,runState】和【线程池有效线程数量,workerCount】。这里可以看到,使用了Integer类型来保存。其中,高3位保存runState,低29位保存了workerCount。上面的COUNT_BITS
值就是29,所以CAPACITY
的值是1左移29,很大很大,大概5亿多。
下面5个字段表示线程池的状态:
RUNNING字段
(1)状态说明:表示线程池出于RUNNING状态。在当前状态,可以接收新任务,以及对线程已添加的任务进行处理。
(2)状态切换:线程池的初始状态就是RUNNING状态。换句话说,线程池一旦被创建就出于这个状态了,并且线程池的任务数量为0
SHUTDOWN字段
(1)状态说明:表示线程池处于SHUTDOWN状态。在当前状态,不接收新任务,但能处理已经添加的任务
- STOP:
- TIDYING:
- TERMINATED:
二、线程池的创建及参数解读
ThreadPoolExecutor提供了两种执行任务的方法:
void execute(Runnable command); // 执行一个任务,没有返回值
Future<?> submit(Runnable task); // 执行一个任务,有返回值
本质上submit中最终还是调用的execute()方法,只不过会将任务包装成一个RunnableFuture返回一个Future对象,用来获取任务执行结果:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
下面是线程池的基本使用示例:
public class ThreadPoolExecutorTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
10,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>()
);
// 演示无返回值
Runnable noReturn = new Runnable() {
@Override
public void run() {
System.out.println("这里经过周密的计算返回:1");
}
};
threadPoolExecutor.execute(noReturn);
// 演示有返回值
Callable haveReturn = ()->{
return "这里经过周密的计算返回:2";
};
Future submit = threadPoolExecutor.submit(haveReturn);
System.out.println(submit.get());
}
// 系统输出:
// 这里经过周密的计算返回:1
// 这里经过周密的计算返回:2
}
在上面的例子中,我们手动新建了一个线程池,没有采用Executors提供的静态工厂方法。ThreadPoolExecutor
的构造方法有4个,但是总的来说算是1个,其他3个都是某些参数采用了默认策略而已。所以这边就拿最全面的那个来给大家介绍一下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
那么,一个任务进入线程线程池添加任务流程及线程增加策略如下: