还不懂Java线程池实现原理
作者:互联网
线程池无论是工作还是面试都是必备的技能,但是很多人对于线程池的实现原理却一知半解,并不了解线程池内部的工作原理,今天一灯就带大家一块剖析线程池底层实现原理。
1. 为什么要使用线程池
使用线程池通常由以下两个原因:
-
频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程。
-
使用线程池可以更容易管理线程,线程池可以动态管理线程个数、具有阻塞队列、定时周期执行任务、环境隔离等。
2. 线程池的使用
/**
* @author 一灯架构
* @apiNote 线程池示例
**/
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,
3,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 2. 往线程池中提交3个任务
for (int i = 0; i < 3; i++) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");
});
}
// 3. 关闭线程池
threadPoolExecutor.shutdown();
}
}
输出结果:
pool-1-thread-2 关注公众号:一灯架构
pool-1-thread-1 关注公众号:一灯架构
pool-1-thread-3 关注公众号:一灯架构
线程池的使用非常简单:
- 调用new ThreadPoolExecutor()构造方法,指定核心参数,创建线程池。
- 调用execute()方法提交Runnable任务
- 使用结束后,调用shutdown()方法,关闭线程池。
再看一下线程池构造方法中核心参数的作用。
3. 线程池核心参数
线程池共有七大核心参数:
参数名称 | 参数含义 |
---|---|
int corePoolSize | 核心线程数 |
int maximumPoolSize | 最大线程数 |
long keepAliveTime | 线程存活时间 |
TimeUnit unit | 时间单位 |
BlockingQueue workQueue | 阻塞队列 |
ThreadFactory threadFactory | 线程创建工厂 |
RejectedExecutionHandler handler | 拒绝策略 |
-
corePoolSize 核心线程数
当往线程池中提交任务,会创建线程去处理任务,直到线程数达到corePoolSize,才会往阻塞队列中添加任务。默认情况下,空闲的核心线程并不会被回收,除非配置了allowCoreThreadTimeOut=true。
-
maximumPoolSize 最大线程数
当线程池中的线程数达到corePoolSize,阻塞队列又满了之后,才会继续创建线程,直到达到maximumPoolSize,另外空闲的非核心线程会被回收。
-
keepAliveTime 线程存活时间
非核心线程的空闲时间达到了keepAliveTime,将会被回收。
-
TimeUnit 时间单位
线程存活时间的单位,默认是TimeUnit.MILLISECONDS(毫秒),可选择的有:
TimeUnit.NANOSECONDS(纳秒)
TimeUnit.MICROSECONDS(微秒)
TimeUnit.MILLISECONDS(毫秒)
TimeUnit.SECONDS(秒)
TimeUnit.MINUTES(分钟)
TimeUnit.HOURS(小时)
TimeUnit.DAYS(天)
-
workQueue 阻塞队列
当线程池中的线程数达到corePoolSize,再提交的任务就会放到阻塞队列的等待,默认使用的是LinkedBlockingQueue,可选择的有:
LinkedBlockingQueue(基于链表实现的阻塞队列)
ArrayBlockingQueue(基于数组实现的阻塞队列)
SynchronousQueue(只有一个元素的阻塞队列)
PriorityBlockingQueue(实现了优先级的阻塞队列)
DelayQueue(实现了延迟功能的阻塞队列)
-
threadFactory 线程创建工厂
用来创建线程的工厂,默认的是Executors.defaultThreadFactory(),可选择的还有Executors.privilegedThreadFactory()实现了线程优先级。当然也可以自定义线程创建工厂,创建线程的时候最好指定线程名称,便于排查问题。
-
RejectedExecutionHandler 拒绝策略
当线程池中的线程数达到maximumPoolSize,阻塞队列也满了之后,再往线程池中提交任务,就会触发执行拒绝策略,默认的是AbortPolicy(直接终止,抛出异常),可选择的有:
AbortPolicy(直接终止,抛出异常)
DiscardPolicy(默默丢弃,不抛出异常)
DiscardOldestPolicy(丢弃队列中最旧的任务,执行当前任务)
CallerRunsPolicy(返回给调用者执行)
4. 线程池工作原理
线程池的工作原理,简单理解如下:
- 当往线程池中提交任务的时候,会先判断线程池中线程数是否是核心线程数,如果小于,会创建核心线程并执行任务。
- 如果线程数大于核心线程数,会判断阻塞队列是否已满,如果没有满,会把任务添加到阻塞队列中等待调度执行。
- 如果阻塞队列已满,会判断线程数是否小于最大线程数,如果小于,会继续创建最大线程数并执行任务。
- 如果线程数大于最大线程数,会执行拒绝策略,然后结束。