这里先看一下阿里巴巴java开发手册里有这样一段说明:
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
本文主要针对Spring的ThreadPoolTaskExecutor做一些简单的说明介绍。 ThreadPoolExecutor是JDK里的,在web开发中,用spring的情况下,一般会用ThreadPoolExecutor。
下面举个简单例子:
线程池的基本配置:
@Configuration
public class ThreadPoolConfig {
@Bean("commonThreadPoolTaskExecutor")
public ThreadPoolTaskExecutor getCommonThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数,在没有设置allowCoreThreadTimeOut(默认false)时,核心线程即使空下来也会一直存在
threadPoolTaskExecutor.setCorePoolSize(5);
// 最大线程数,最多能容纳的线程数
threadPoolTaskExecutor.setMaxPoolSize(64);
// 除核心线程外,线程执行完任务空下来多久自动退出,但如果allowCoreThreadTimeOut=true,这个时间也适用于核心线程数
threadPoolTaskExecutor.setKeepAliveSeconds(300);
// 等待队列,超过核心线程数的线程会到这里
threadPoolTaskExecutor.setQueueCapacity(32);
// 线程池的名称,用于定位任务所在的线程池
threadPoolTaskExecutor.setThreadNamePrefix("commonThreadPool");
// 拒绝策略,当线程超过maxPoolSize时的处理方式,这里通过实现RejectedExecutionHandler自定义拒绝策略,方便日志或持久化
threadPoolTaskExecutor.setRejectedExecutionHandler(new CommonRejectedExecutionHandler());
return threadPoolTaskExecutor;
}
public static class CommonRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// TODO
}
}
}
来个使用例子,在controller里用线程池打印几个日志,这里用到了execute、submit两个方法:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
@Qualifier("commonThreadPoolTaskExecutor")
private ThreadPoolTaskExecutor commonThreadPoolTaskExecutor;
@GetMapping("/threadTest")
public String threadTest() {
System.out.println("-- start --");
commonThreadPoolTaskExecutor.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException ignored) { }
System.out.println("execute running");
});
commonThreadPoolTaskExecutor.submit(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException ignored) { }
System.out.println("submit running");
});
System.out.println("-- end --");
return "success";
}
}
输出的结果:
-- start --
-- end --
execute running
submit running
可以看到,主线程很快执行完,线程池里耗时操作在后面才打印出来。
再说一下execute、submit两个方法的区别,如果在不需要返回值的情况下,两个方法在使用上基本没区别,但是如果需要线程池里方法执行后的返回值,就需要用submit,这时候主线程会在线程池里的子线程完成结束后才结束。
来看例子:
@GetMapping("/threadTest")
public String threadTest() {
System.out.println("-- start --");
Future<String> future = commonThreadPoolTaskExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException ignored) { }
System.out.println("submit running");
return "abc";
}
});
try {
System.out.println("submit返回值:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("-- end --");
return "success";
}
-- start --
submit running
submit返回值:abc
-- end --
当有 future.get() 时,主线程会在子线程跑完之后在执行,因为Callable是有阻塞机制的。