前言
每个事物的存在都有其存在的意义。同样线程池的产生一定也有其产生的原因。具体是什么原因呢?
我们都知道线程是稀缺资源,每次创建线程都需要为其分配工作空间,同时由于每次任务来临都创建线程会增加线程的创建和销毁。频繁地创建和销毁都涉及到系统调用,比较消耗系统资源,所以就引入了线程池技术。
线程池基本概念
线程池包括以下四个基本组成部分:
- 线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
- 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完成的收尾工作,任务的执行状态等;
- 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
线程池工作原理
为了能够更好的理解线程池的工作原理,我打算从一个思想实验一步一步引入线程池。
从一段最简单的旅程开始,假设有一段代码,我希望异步执行它,应该是这样的:
new Thread(() -> {
System.out.println("Hello World!");
}).start();
这样写完成功能丝毫没有问题。但是我想到了一个问题:我这样写,大家都这样写,程序中到处都是这样创建线程的代码?
然后我就想办法写一个统一的工具类来让大家调用,考虑到方便以后的扩展,我先定义一个接口吧:
public interface Executor {
void execute(Runnable command);
}
然后呢我再实现一下这个接口:
public class ThreadPoolExecutorDemo implements Executor {
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
}
很轻松,我实现了之前的小目标。但是呢,我又想到了一个问题:假如有1000个人同时调用了这个工具类提交任务,那就会创建1000个线程出来,不可控的事情我可不敢干,能不能控制一下线程数量呢?
我开始思考,我能否把每次提交的任务都丢到队列里去呢,然后只启动一个线程,不停的从队列里取任务,执行任务,这样就永远只有一个线程在运行。
这样一来,我不仅解决了线程数量的问题,还将任务的提交和执行解耦了。但是只有一个线程可不行啊,是不是有点太少了?那我就增加线程数量吧,但具体要增加多少呢,还是让调用者来决定吧,暂且就叫核心线程数corePoolSize吧。
我又开始调整我的设计了:
- 在初始化线程的时候,直接启动
corePoolSize个工作线程先跑着; - 这些工作线程死循环的从队列里取任务,然后执行;
- 任务仍然直接放到队列里,但队列满了之后就直接抛弃。
至此,好像一个满足基本需求的线程池就诞生了。但是好像又有问题了:这个任务队列一满就直接把任务丢弃的做法,是不是有点简单粗暴了呢?还有,初始化时就创建一堆的工作线程空跑,也太浪费了吧?
想到此,我开始有点难到了,喝点咖啡吧,边喝边想。我想到是不是可以每次提交任务的时候再创建线程呢,每次都记录一下已经创建起来的线程数量。然后我是不是可以再增加一个策略,在每次任务提交失败后,由调用者来决定需要怎样处理。
于是我又开始着手设计:
- 我先定义一个变量叫
workCount用来记录工作线程数量。 - 然后我增加了一个线程工厂
ThreadFactory,每次增加工作线程的时候不再直接new线程,而是调用这个ThreadFactory实现类的new Thread方法。 - 然后我再增加一个拒绝策略:
RejectedExecutionHandler,在任务提交失败后,执行rejectedExecution方法。
当任务提交workCount<corePoolSize时:
当任务提交workCount=corePoolSize时,再提交任务:
当工作线程和任务队列都满了以后:
一切看似完美了,这个时候有机灵鬼要说了:我想要它能够弹性伸缩,不然我怎么应对QPS很高或者很低的场景呢?
我想了想似乎也不能把corePoolSize设置得过大,这样同样会导致资源浪费,那怎么办呢?我灵机一动又想到了,我再增加一个最大线程数maximumPoolSize。当核心线程数和任务队列都满了,新提交的任务任然可以通过创建新的工作线程,直到线程数达到maximumPoolSize为止。
这样在高峰时期的弹性扩容我们就搞定了,那么在低谷的时候该怎么处理呢?
当长时间没有任务提交时,核心线程与非核心线程都是在空跑,还是浪费资源。
那我就想着给非核心线程设定一个超时时间keepAliveTime吧。当这么长时间都没能从队列中获取任务,就直接销毁线程了。
好了,现在在QPS高峰时低谷时我们已经可以从容面对了。啪!上图:
这就是我们以上整个思想实验的结果。这就是Java的JUC包下的ThreadPoolExecutor类。到此,我觉得再有人问你线程池原理是不是丝毫不慌了呢。
巩固一下,再做个总结:
我可再也不想背八股文了!!!