一:线程池介绍
1.1 概念
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程
1.2 工作机制
CPU将任务提交给线程池,线程池拿到任务后,在内部寻找空闲的线程来处理任务
1.3 使用原因
多线程运行时,频繁的进行线程的创建和销毁,会过度的消耗系统资源,增加并发变成风险
1.4 组成部分
-
线程池管理器(ThreadPoolManage):用来创建并管理线程池
-
工作线程(workThread):线程池中的线程
-
任务队列(Queue):用于存放没有处理好的任务,提供一种缓冲机制
-
任务接口(Task):每个任务必须实现的接口 ,以供工作线程调度任务的执行
1.5 作用
- 利用线程池管理并复用线程、控制最大并发数等
- 实现任务线程队列缓存策略和拒绝机制
- 实现某些与时间相关的功能,如定时执行、周期执行
- 隔离线程环境。当不同服务之间资源消耗相差过大时,配置独立的线程池,可以避免各服务线程相互影响
1.6 执行过程
二:四种常见的线程池
线程工厂Executors实现的种类
实现 | 名称 | 使用的队列 | 说明 |
---|---|---|---|
FixedThreadPool | 固定大小线程池 | LinkedBlockingQueue | 固定线程数量,不存在空闲线程,任务队列无界 |
WorkStealingPool | 并行线程池 | FIFO_QUEUE | 线程数量不限制,使用ForkJoinPool分治法来执行任务 |
CachedThreadPool | 缓冲线程池 | SynchronousQueue | 核心线程数为0,最大线程数量为Integer.MAX_VALUE,自己根据cpu性能决定线程数量 |
ScheduledThreadPool | 定时任务线程池 | DelayedWorkQueue | 最大线程数量为Integer.MAX_VALUE,任务按时间优先级执行,不回收工作线程 |
SingleThreadExecutor | 单线程线程池 | LinkedBlockingQueue | 线程数量1个,任务按加入顺序执行 |
补充
队列 | 说明 |
---|---|
LinkedBlockingQueue | 基于链表和显示锁实现的队列 |
SynchronousQueue | 基于双栈/双队列和显示锁实现的交付队列 |
DelayedWorkQueue | 基于优先级队列和显示锁实现的延迟队列,按照执行时间的升序来排序,执行时间举例当前时间越近的任务在队列前面 |
2.1 newSingleThreadExecutor
2.1.1 使用的小例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author miao
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
ExecutorService singleThread = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3;i++){
int finalI = i;
singleThread.execute(()->{
try {
System.out.println(finalI);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
2.1.2 源码阅读
总结
newSingleThreadExecutor的核心线程数和最大线程数都是1,所以该线程池中只有一个线程执行任务,而队列使用的是无界队列LinkedBlockingQueue(队列的容量为Integer.MAX_VALUE),可能会造成OOM异常
2.2 newCachedThreadPool
2.2.1 使用的小例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author miao
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 3;i++){
int finalI = i;
cachedThreadPool.execute(()->{
try {
System.out.println(finalI);
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
2.2.2 阅读源码
总结
newCachedThreadPool 核心线程数为0,最大线程数为Integer.MAX_VALUE,简单来说就是遇强则强 自己根据cpu性能决定线程数量
2.3 newFixedThreadPool
2.3.1 使用的小例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author miao
*/
public class ThreadPoolExecutorDemo {
private static final int THREAD_COUNT = 3;
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < 6; i++) {
int finalI = i;
fixedThreadPool.execute(() -> {
try {
System.out.println(finalI);
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
2.3.2 阅读源码
总结
固定线程池 默认活跃线程数,最大线程数都为方法形参,这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止,而队列使用的是无界队列LinkedBlockingQueue(队列的容量为Integer.MAX_VALUE),可能会造成OOM异常
2.4 newScheduledThreadPool
2.4.1 使用的小例子
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author miao
*/
public class ThreadPoolExecutorDemo {
private static final int THREAD_COUNT = 3;
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(THREAD_COUNT);
scheduledThreadPool.schedule(
() -> System.out.println("延迟三秒执行"),
3,
TimeUnit.SECONDS);
}
}
2.4.2 阅读源码
由于ScheduleThreadPoolExecutor继承了ThreadPoolExecutor,所以线程池的实现还是父类的构造方法
总结
ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期执行任务,它的功能与Timer类似,但却更强大,更灵活。Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数
三:ThreadPoolExecutor
其实这四种线程池最终的实现方式都是ThreadPoolExecutor的构造方法
下面对参数进行说明
参数 | 解释 |
---|---|
corePoolSize | 常驻核心线程数。如果等于0,则任务执行完后没有任何请求进入时销毁线程池的线程;如果大于0,及时本地任务执行完毕,核心线程也不会被销毁 |
maximumPoolSize | 表示线程池能够容纳同时执行的最大线程数,必须大于等于1。如果当阻塞队列已满时,并且当前线程池线程个数没有超过 maximumPoolSize 的话,就会创建新的线程来执行任务。 |
keepAliveTime | 空闲线程存活时间。如果当前线程池的线程个数已经超过了 corePoolSize,并且线程空闲时间超过了 keepAliveTime 的话,就会将这些空闲线程销毁,直到只剩下corePoolSize个线程为止,但是当ThreadPoolExecutor的allowCoreThreadTimeOut设置为true时,核心线程超时后也会被回收 |
unit | 时间单位。为 keepAliveTime 指定时间单位,通常为 TimeUnit.SECONDS |
workQueue | 阻塞队列,用于保存任务。可以使用 ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue |
threadFactory | 线程工厂,用来生成一组相同任务的线程。线程池的命令是通过这个factory增加组名前缀来实现的,在虚拟机分析时,就可以直到线程任务是由哪个线程工厂产生的 |
handler | 执行拒绝策略的对象。当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况 1. AbortPolicy(用的最多):直接拒绝所提交的任务,并抛出RejectedExecutionException异常 2. CallerRunsPolicy:只用调用者所在的线程来执行任务 3. DiscardPolicy:不处理直接丢弃掉任务 4. DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务 |