线程池
线程的频繁创建和销毁,使用了大量的资源,尤其在并发情况下,对性能影响很大
解决:提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中
优点:
- 提高响应速度(减少创建新线程时间)
- 降低资源消耗
- 便于线程管理
- corePoolSize:核心池大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后终止
线程池使用
JDK5.0提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口
- void execute(Runnable command):执行任务,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown:关闭连接池
Executors:工具类、连接池的工厂类,用于创建并返回不同类型的线程池
public class ThreadTest2 {
public static void main(String[] args) {
// 创建线程池,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 启动线程
service.execute(new ThreadTest());
service.execute(new ThreadTest());
service.execute(new ThreadTest());
// 关闭线程池
service.shutdown();
}
}
class ThreadTest implements Runnable {
@Override
public void run() {
System.out.println("执行");
}
}
ThreadPoolExecutor
ThreadPoolExecutor是JUC包中线程池的核心实现类,是Java中创建线程池最正统、最灵活的方式
7个核心参数
public ThreadPoolExecutor(
int corePoolSize, // 1. 核心线程数
int maximumPoolSize, // 2. 最大线程数
long keepAliveTime, // 3. 存活时间
TimeUnit unit, // 4. 时间单位
BlockingQueue<Runnable> workQueue, // 5. 任务队列
ThreadFactory threadFactory, // 6. 线程工厂
RejectedExecutionHandler handler // 7. 拒绝策略
)
任务执行的完整流程
当一个任务通过 execute() 提交到线程池时,会按照以下4步执行:
- 判断核心线程:如果当前运行的线程数 < corePoolSize,则直接创建新线程执行任务(即使有空闲核心线程也创建)
- 判断阻塞队列:如果当前线程数 >= corePoolSize,则尝试将任务放入 workQueue
- 判断最大线程:如果队列已满,则判断当前线程数 < maximumPoolSize,如果是,则创建临时线程执行任务
- 执行拒绝策略:如果线程数已达到 maximumPoolSize 且队列也满了,则触发 handler 的拒绝策略
代码示例:自定义线程池
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
// 核心参数配置
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2); // 有界队列,容量2
ThreadFactory threadFactory = new ThreadFactory() {
private int count = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "MyThreadPool-" + count++);
}
};
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit,
workQueue, threadFactory, handler
);
// 提交 7 个任务(模拟流程)
for (int i = 0; i < 7; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskId);
try {
Thread.sleep(2000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
执行流程推演(任务耗时2秒):
- 提交任务 1、2:核心线程数2,直接执行(线程 A、B)
- 提交任务 3、4:核心线程满,放入队列(队列容量2,已满)
- 提交任务 5、6:队列满,创建临时线程(线程 C、D),达到最大线程数4
- 提交任务 7:线程池满、队列满,触发 CallerRunsPolicy,由 main 线程自己执行
为什么不建议使用 Executors 工厂类
Executors 提供的几种快捷方法(如 newFixedThreadPool、newCachedThreadPool)本质上也是调用 ThreadPoolExecutor,但它们存在隐患:
- newFixedThreadPool:队列是 LinkedBlockingQueue(无界队列),任务过多会导致 OOM(内存溢出)
- newCachedThreadPool:maximumPoolSize 是 Integer.MAX_VALUE,线程数无限,同样会导致 OOM