前言
线程池的作用:
- java中的线程是基于内核线程实现的,这样就会带来两个问题
- 线程的创建需要进行系统调用,这样就会在用户态和内核态进行切换,导致较多的上下文切换
- 因为java线程和内核线程是1:1的,那么每个线程都得消耗一定的内核空间,因为需要维护线程栈。
- 所以线程池的作用就可以提高资源利用率,可以重复使用已经创建号的线程,减少重复创建,销毁造成的开销。
一、使用案例
这里我们创建了一个线程池,并且批量往线程池中提交任务。
public class ThreadPoolExecutorTest {
private static final int taskCount = 50;//任务数
public static void main(String[] args) throws InterruptedException {
AtomicInteger integer = new AtomicInteger();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10,//核心线程数
20,//最大线程数
5,//非核心回收超时时间
TimeUnit.SECONDS,//超时时间单位
new ArrayBlockingQueue<>(30)//任务队列);
System.out.println("总任务数:" + taskCount);
long start = System.currentTimeMillis();
//模拟任务提交
for (int i = 0; i < taskCount; i++) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(500);//模拟执行耗时
System.out.println("已执行" + integer.addAndGet(1) + "个任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
try {
//注意这里我try起来了,默认拒绝策略会报错
executor.execute(thread);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
long end = 0;
while (executor.getCompletedTaskCount() < 50) {
end = System.currentTimeMillis();
}
System.out.println("任务总耗时:" + (end - start));
}
}
二、重要组件
从上面的使用案例中我们发现线程池中有几个比较重要的组件。
2.1、corePoolSize(核心线程数)
线程池内部需要维持的一个最小的工作线程数量。工作线程数量不足这个数量的时候,新来的任务都会交给一个新建的工作线程,任务不会被放入队列。工作线程数达到这个数量的时候,新来的任务都会直接放入队列,所有的工作线程会主动去轮询领任务,这里每新来一个任务就创建一个核心线程主要是为了保证核心线程尽快达到我们设置的数量,这样如果之后有很多任务涌进来,这些已创建好的核心线程就可以马上做好准备处理这些任务了。
2.2、maximumPoolSize(最大线程数)
线程池内部会限制一个最大的工作线程数量。当阻塞队列满了,并且当前线程数量已经大于等于核心线程数了,那么只有小于最大线程数的时候才可以继续创建线程处理。
2.3、workQueue(任务队列)
线程池内部维护了一个队列用来存储待处理的任务,每个工作线程都可以从队列获取任务进行处理,这里任务放入队列的时机是:核心线程数都正在执行,没时间处理,那么会先把任务放入队列中。
这里你可能想问为啥不再继续创建线程,反正还没到最大线程数,再创建线程当然可以,但是如果核心线程数处理的很快,可能过了很短的时间就能空闲出来继续处理任务,那么就可以避免掉线程频繁创建销毁的开销了。
2.4、RejectedExecutionHandler(拒绝策略)
线程池如果workerQueue是有界队列并且已经满了,并且线程数也到了最大线程数的上限了,此时仍然有任务一直提交过来,但是现在已经没办法处理了,已经超负载了,这个时候就需要有一种处理机制来帮我拒绝掉这些任务。
线程池默认提供了AbortPolicy,DiscardPolicy,DiscardOldestPolicy,CallerRunsPolicy,以及自定义策略这五种拒绝策略,默认采用的是 AbortPolicy
2.5、keepAliveTime(线程存活时间)
这个是线程保持空闲的时间,可能很长一段时间都没有任务提交过来,那么线程池中的线程都处于空闲状态,但是他们还占用资源,所以当线程数量超过了核心线程数,并且有线程超过了keepAliveTime时间一直处于空闲状态,那么就会把他干掉,直到线程数减少到核心线程数。
注:默认是针对非核心线程数,如果我们把参数allowCoreThreadTimeOut设置为true,那么针对核心线程数也生效
三、线程池处理流程

四、线程池状态机
| 线程池状态 | 状态描述 |
|---|---|
| RUNNING | 正常的运行状态,此时可以正常接收和处理任务 |
| SHUTDOWN | 关闭状态,只能通过调用shutdown()方法达到此状态。此时可以处理组阻塞队列中的任务,但不再接受新任务,并且中断空闲的工作线程 |
| STOP | 停止状态,只能通过调用shutdownNow()方法达到此状态。此时清除队列中的未处理的任务,中断所有的(空闲的+正在执行任务的)工作线程。(不建议直接调用shutdownNow,会导致业务逻辑异常终止,会带来很多不可预知的问题) |
| TIDYING | 所有的任务都执行完毕后,(同时也涵盖了阻塞队列中的任务),当前线程池中的活动的线程数量降为0,从SHUTDOWN和STOP自动流转到此状态,然后将会调用terminated方法。 |
| TERMINATED | 线程池的终止状态,从TIDYING状态自动流转到此状态,当terminated方法执行完毕后,线程池会进入该状态 |

五、线程池状态及线程数管理
- 这里把一个int类型的变量复合使用,高3位表示线程池运行状态,低29位表示线程池线程数量
- 线程池对应状态(变化的只有高3位):
- 运行状态: 101 00000 00000000 00000000 00000000
- 关闭状态 : 000 00000 00000000 00000000 00000000
- 强制关闭 : 001 00000 00000000 00000000 00000000
- 结束状态 : 010 00000 00000000 00000000 00000000
- 死亡状态 : 011 00000 00000000 00000000 00000000
- 提供了3个函数,用于获取ctl的相关状态
- runStateOf:获取当前线程池的运行状态
- c:运行时的ctl状态值
- ~CAPACITY:0,原本是29个1,线程取反变成29个0了。
- c & ~CAPACITY:c是32位bit的值,然后与上29个0,那么低29位就全变成0了,那么最后取到的就是ctl的高3位的值了。
- c : 运行期间的ctl状态值
- CAPACITY : 29个1
- c & CAPACITY : c和29个1做与操作,那么高3位就变成0了,所以最终只保留低29bit位,由此可以得到当前线程池中已创建的工作线程数量
- ctlOf(int rs, int wc) :
- rs : 当前需要改变的最新运行状态,例如由RUNNING变为SHUTDOWN的情况下,那么rs传送的就是SHUTDOWN值
- wc : 当前线程池中已创建的工作线程数量,根据workerCountOf()获取
- rs | wc : 最终是高3bit位更换了状态,低29bit位保留了当前创建的工作线程数
//运行时的状态,默认初始化后就是RUNNING状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//29bit位,Integer.size=32
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池中工作线程的最大容量: 1左移29位减1 ⬇️
// 11111 11111111 11111111 11111111 (29个1)
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//运行状态: 101 00000 00000000 00000000 00000000
private static final int RUNNING = -1 << COUNT_BITS;
// 关闭状态 : 000 00000 00000000 00000000 00000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 强制关闭 : 001 00000 00000000 00000000 00000000
private static final int STOP = 1 << COUNT_BITS;
// 结束状态 : 010 00000 00000000 00000000 00000000
private static final int TIDYING = 2 << COUNT_BITS;
// 死亡状态 : 011 00000 00000000 00000000 00000000
private static final

线程池通过复用线程提高资源利用率,减少创建和销毁线程的开销。文章详细介绍了ThreadPoolExecutor的使用,包括核心线程数、最大线程数、任务队列、拒绝策略等组件,以及线程池的处理流程、状态机和执行流程源码分析。线程池的状态从RUNNING到TERMINATED,通过shutdown()和shutdownNow()方法控制,线程池在不同状态下如何处理任务和线程的管理。
最低0.47元/天 解锁文章

6839

被折叠的 条评论
为什么被折叠?



