为什么不建议使用Executors创建线程池?
1.引言
为什么开发过程中都不建议使用 Executors
创建线程池,不建议使用 Executors
创建为什么Java还提供这样一个创建类。开发过程中完全不能用 Executors
创建线程池吗,带着这几个问题我们从源码以及开发过程来说明一下几个问题:
2. Java提供Executors的猜想
Executors在Java中其实就是一个充当一个工具类,对 Executor, ExecutorService, ScheduledExecutorService, ThreadFactory 提供语义化的服务。让使用者能够更好的使用在不同的业务中使用的不同的线程池。提供一个统一化的线程池工厂来创建。例如:
-
创建一个固定大小的线程就可以使用
Executors.newFixedThreadPool(int nThreads)
在创建的时候这个就很明确,一看就是创建一个固定大小的线程池,那么线程数量多少就是传入的参数nThreads
-
创建一个单线程的线程池
Executors.newSingleThreadExecutor()
这样创建完成连参数都省略了
所以 Java提供Executors为了创建各类线程池提供方便,符合语义。既然是这样那么为什么又不建议使用呢?这不是自相矛盾吗
3. 为什么不建议使用Executors创建线程池
为什么不建议使用Executors创建线程池我们从源码以及示例来分析。我们以创建固定线程池为例(其他的可以触类旁通都差不多),Executors创建固定线程池方法如下图:
-
设置固定线程池大小
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
} -
设置线程池大小和自定义实现ThreadFactory
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
通过代码可以发现两者的区别在于是否自定义实现ThreadFactory。里面都是创建ThreadPoolExecutor。
3.1 Executors与直接创建ThreadPoolExecutor优劣
方式1和方式2创建线程池相比直接使用ThreadPoolExecutor创建的优势在于:简单,语义明确。
但是方式1创建线程池存在用户自身无法控制线程池名称以及队列的长度,队列使 用的是无界队列,方式2虽然可以自定义线程池名称但是相比线程池名称更加应该关注的是队列,和方式1一样都是使用的无界队列。所以方式1和方式2劣势相比比直接使用ThreadPoolExecutor创建线程池的劣势在于:无法控制队列的长度这个也是不推荐使用Executors主要原因 。
Tips:通过LinkedBlockingQueue的源码发现,允许的请求队列长度为 Integer.MAX_VALUE
用代码来演示一下如果不停的创建可能会发生的问题:
/**
* @author mxsm
* @date 2022/2/9 22:01
* @Since 1.0.0
*
* -Xms5m
* -Xmx5m
*/
public class Phenomena1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
for(;;){
executorService.submit(new Work());
System.out.println(System.currentTimeMillis());
}
}
static class Work implements Runnable{
private StringBuilder builder = new StringBuilder();
public Work() {
for(int i = 0; i < 10000000; ++i){
builder.append(i);
}
System.out.println(builder.toString().getBytes(StandardCharsets.UTF_8).length/1024);
}
@Override
public void run() {
try {
System.out.println(System.currentTimeMillis());
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
Tips:将内存调小
在以LinkedBlockingQueue作为队列情况下,会导致大量的请求堆积最终可能导致OOM
3.2 线程执行问题回溯
在线程池执行发生错误的时候,这个时候就需要根据错误堆栈进行追踪,如果你创建线程如下:
public class Phenomena1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
ExecutorService executorService1 = Executors.newFixedThreadPool(2);
executorService.submit(new Runnable() {
@Override
public void run() {
}
});
executorService1.submit(new Runnable() {
@Override
public void run() {
}
});
}
}
运行程序然后用java命令查看线程池:
jsp
jstack -l <pid>
看一下线程的情况
通过上图并不知道这两个线程属于哪个线程池。
所以在使用Executors创建线程池很多人不设置自定义的ThreadFactory,这也是不建议使用Executors的原因。