线程池简介
-
Java的线程与系统的线程一一对应,创建线程的系统资源开销比较大。
-
过多的线程会占用更多的系统资源,线程的上下文切换也会带来系统资源开销。
-
线程池可以对线程进行统一管理,平衡线程与系统资源直接的关系,提高系统资源利用率与程序稳定性。
-
创建一个自定义线程池代码如下:
// 使用指定参数创建一个线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, // 最大核心线程数量 2, // 最大线程数量 = 核心线程数量 + 临时线程数量 1000, // 临时线程的最大空闲时间,超过这个时间将会被销毁 TimeUnit.MILLISECONDS, // 时间单位 new ArrayBlockingQueue<Runnable>(1), // 任务队列的容器 new MyThreadFactory(), // 线程创建工厂类 new MyRejectedExecutionHandler() // 任务拒绝处理策略 ); // 向线程池提交一个任务 executor.submit(new MyRunner("hello"));
线程池相关参数
-
corePoolSize:最大核心线程数量,常驻线程。
-
maximumPoolSize:最大线程数量,临时线程。
-
keepAliveTime:临时线程的最大空闲时间,超过这个时间的临时线程将会被销毁。
-
unit:空闲时:间的单位。
-
workQueue:存放任务的阻塞队列。JDK提供的有:
- LinkedBlockingQueue:链表队列,最大长度为int类型最大值。
- SynchronousQueue:转发队列,长度为0,只对任务转发,不存储任务。
- DelayedWorkQueue:延迟队列,数据结构为堆,按照任务延迟时间排序。
-
threadFactory:线程创建工厂类,可以通过实现
java.util.concurrent.ThreadFactory
接口创建自己的工厂类。 -
handler:任务拒绝策略,线程池无法处理任务时执行的策略,可以通过实现
java.util.concurrent.RejectedExecutionHandler
接口创建自己的策略。JDK提供的策略如下:
- AbortPolicy:拒绝任务时会抛出一个异常。
- DiscardPolicy:拒绝任务时不会有任何提示,有数据丢失风险。
- DiscardOldestPolicy:拒绝任务时丢弃存活时间最长的任务。
- CallerRunsPolicy:拒绝任务时,将任务给提交的线程执行,同时可以减缓任务提交线程的提交速度,减轻线程池压力。
线程池任务执行
- 首先任务提交到线程池由核心线程执行。
- 当线程任务执行达到核心线程最大数量时,将会把任务提交给任务队列存储。
- 当任务队列满时,就会继续创建临时线程执行任务,直到达到最大线程数量。
- 任务执行达到最大线程数量时,此时线程池无法再接收新的任务,将会进入拒绝策略的执行。
- 任务执行数降下后将会根据临时线程空闲时间去销毁临时线程,减少资源的消耗。
JDK内置线程池
线程池实现类 | 使用的阻塞任务队列 | 线程池特点 | 使用注意事项 |
---|---|---|---|
Executors.newFixedThreadPool(5); | LinkedBlockingQueue | 固定线程数量,任务队列容量为int最大值 | 任务队列可能导致OOM |
Executors.newCachedThreadPool(); | SynchronousQueue | 核心线程数量为int最大值,任务队列仅作转发 | 线程过多可能导致OOM |
Executors.newSingleThreadExecutor(); | LinkedBlockingQueue | 核心线程数为一,线程异常将会创建新的线程执行任务,可以保证任务执行的顺序 | 任务队列可能导致OOM |
Executors.newScheduledThreadPool(5); | DelayedWorkQueue | 指定频率执行任务,多个核心线程执行 | 任务队列可能导致OOM |
Executors.newSingleThreadScheduledExecutor(); | DelayedWorkQueue | 指定频率执行任务,单个核心线程执行 | 任务队列可能导致OOM |
线程数量选择
-
JDK内置线程有各种缺陷,我们要根据具体的情况定制自己的线程池。
-
CPU计算密集型的任务推荐核心线程数量为cpu核心数的2倍,线程过多会导致频繁的线程上下文切换,从而降低了执行效率。
-
IO密集型的任务推荐线程核心数为cpu核心数的数倍,因为IO读取速度相对较慢,更多的线程可以提高cpu利用率。
-
《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法:
线程数 = CPU核心数 *( 1 + 平均等待时间 / 平均工作时间 )
-
过多过少的线程数量都会影响任务执行速度,我们可以根绝任务进行分类,按照不同的线程池进行执行。具体还需注意系统资源的协调。
自定义线程池
package cn.devzyh.learning.threadpool;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义线程池参数
*/
public class ThreadPoolParamTest {
/**
* 实现自己的线程创建工厂类
* 也可以通过第三方工具类创建,比如:
* 通过com.google.common.util.concurrent.ThreadFactory的Builder来实现
*/
static class MyThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public MyThreadFactory() {
this.namePrefix = "TestPool-" +
poolNumber.getAndIncrement() +
"-TestThread-";
}
@Override
public Thread newThread(Runnable r) {
// 指定自定义的线程名称
return new Thread(r, this.namePrefix + threadNumber.getAndIncrement());
}
}
/**
* 实现自己的任务拒绝处理策略
* 超过线程池处理能力的任务会执行此策略
* Java内置四种拒绝策略:
* AbortPolicy: 抛出一个AbortExecutionException的异常
* DiscardPolicy: 丢弃新提交的任务,没有任何通知
* DiscardOldestPolicy: 丢弃任务队列头部的任务,没有任何通知
* CallRunsPolicy: 将任务丢给提交任务的线程执行,即可以执行任务又可以延缓新任务的提交
*/
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("线程池已满,1秒后重试任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.submit(r); // 重试
System.out.println("任务以再次提交到线程池");
}
}
/**
* 线程任务
*/
static class MyRunner implements Runnable {
private String taskName;
public MyRunner(String name) {
this.taskName = name;
}
@Override
public void run() {
System.out.println("开始执行任务[" + this.taskName + "],当前线程名称:" +
Thread.currentThread().getName());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行完毕[" + this.taskName + "]");
}
public String getTaskName() {
return this.taskName;
}
}
public static void main(String[] args) {
// 创建自定义参数的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, // 最大核心线程数量
2, // 最大线程数量 = 核心线程数量 + 临时线程数量
1000, // 临时线程的最大空闲时间,超过这个时间将会被销毁
TimeUnit.MILLISECONDS, // 时间单位
new ArrayBlockingQueue<Runnable>(1), // 任务队列的容器
new MyThreadFactory(), // 线程创建工厂类
new MyRejectedExecutionHandler() // 任务拒绝处理策略
);
executor.submit(new MyRunner("core")); // 此时会创建一个核心线程执行任务
executor.submit(new MyRunner("queue")); // 此时核心线程数达到最大,当前任务会被提交到任务队列等待
executor.submit(new MyRunner("temp")); // 此时任务队列已满,将创建临时线程执行任务
executor.submit(new MyRunner("reject")); // 此时超出线程执行任务能力,执行任务拒绝策略
executor.shutdown(); // 关闭线程池
}
}