转载:Java 线程池
线程池的创建方式
方式一:创建单一线程的线程池 newSingleThreadExecutor
特点:
- 线程池中只包含 1 个线程,存活时间是无限的
- 按照提交顺序执行任务
- 唯一线程繁忙时,新提交的任务会被加入到阻塞队列中的,阻塞队列是无限的
- 适用场景:要求任务按ti执提交次序执行时
static class TargetTask implements Runnable {
public static final int SLEEP_GAP = 1000;
// AtomicInteger 提供了一种线程安全的方式来对整数进行操作。它可以保证多个线程同时对同一个AtomicInteger对象进行操作时,不会出现数据竞争和不一致的情况
static AtomicInteger taskNo = new AtomicInteger(0);
String taskName;
@Override
public void run() {
log.info(taskName + "is running ...");
try{
Thread.sleep(SLEEP_GAP);
}catch (Exception e){
log.error(taskName + "running error !");
e.printStackTrace();
}
log.info(taskName + "is end ...");
}
TargetTask(){
taskName = "task_" + taskNo;
taskNo.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
// 方式一:创建单一线程的线程池 newSingleThreadExecutor
ExecutorService poll1 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 2; i++) {
poll1.execute(new TargetTask());
poll1.submit(new TargetTask());
}
poll1.shutdown();
}
}
方式二:创建指定线程数量的线程池 newFixedThreadPool
- 如果线程池中任务没有达到『固定数量』,每次提交任务线程池中就会创建新的线程。
- 如果线程数量达到『固定数量』,新提交的任务就会加到阻塞队列中,并且阻塞队列是无界的。
- 如果线程池中的线程因为执行任务而异常了,那么线程池中就会补一个新的线程
- 适用场景:需要任务长期执行的场景,CPU 密集型场景
- 缺点:当突增大量任务时,阻塞队列无限增大,服务器资源迅速耗尽
// 方式二:创建指定线程数量的线程池 newFixedThreadPool
ExecutorService poll2 = Executors.newFixedThreadPool(3);
for (int i = 0; i < 2; i++) {
poll2.execute(new TargetTask());
poll2.submit(new TargetTask());
}
poll2.shutdown();
如图:最多只有3个线程同时进行
方式三:创建可缓存的线程池 newCachedThreadPool
- 新增加任务时,如果线程池内的线程均繁忙,则会新增加线程来执行
- 如果部分线程空闲,
超过60s则会进行线程回收,则被终止并移出缓存
- 适用场景:需要快速处理突发性强,耗时短的线程,如Netty的NIO处理场景、REST API接口的瞬时削峰场景
- 缺点:当突增大量任务时,创建线程过多导致资源耗尽
ExecutorService poll3 = Executors.newCachedThreadPool();
for (int i = 0; i < 2; i++) {
poll3.execute(new TargetTask());
poll3.submit(new TargetTask());
}
poll3.shutdown();
如图:只要有新的任务,就会开辟线程
方式四:创建可调度线程池 newScheduledThreadPool
- 首次执行任务可以设置延迟时间
- 具有周期性,主线程睡眠时间越长,线程池中的周期次数越多
ScheduledExecutorService poll4 = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 5; i++) {
// 参数1: task任务
// 参数2: 首次执行任务的延迟时间
// 参数3: 周期性执行的时间
// 参数4: 时间单位
poll4.scheduleAtFixedRate(new TargetTask(),0,500, TimeUnit.MILLISECONDS);
}
Thread.sleep(4000); //主线程睡眠时间越长 周期次数越多
poll4.shutdown();
如图:周期性的执行任务
方式五: ThreadPoolExecutor 标准的创建方式
- 最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装
// 方式五: ThreadPoolExecutor 标准的创建方式,上面1-3 种方式都是ThreadPoolExecutor的变种
// 参数1: 核心线程数量
// 参数2: 最大线程数量
// 参数3: 核心线程之外的空闲线程的存活时间
// 参数4: 存活时间的单位
// 参数5: 阻塞队列
// 下面这种形式类似于方式二,创建固定数量的线程数
ExecutorService poll5 = new ThreadPoolExecutor(3, 3,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < 2; i++) {
poll5.execute(new TargetTask());
poll5.submit(new TargetTask());
}
poll5.shutdown();
如图:最多启动3个线程执行任务
总结:
- newFixedThreadPool和newSingleThreadExecutor: 阻塞队列无界,会堆积大量任务导致OOM(内存耗尽)
- newCachedThreadPool和newScheduledThreadPool: 线程数量无上界,会导致创建大量的线程,从而导致OOM
- 建议直接使用线程池ThreadPoolExecutor的构造器