创建线程池时为什么不建议使用Executors进行创建
有没有想过为什么在创建线程池的时候我们一般都是通过ThreadPoolExecutor来创建线程池,很少使用Executors来创建线程池?
实践出真知,让我们具体在代码里面看看是什么原因~
我们先用Executors来创建一个固定线程的线程池:
@Test
public void test2(){
ExecutorService executorService1 = Executors.newFixedThreadPool(10);
for(int i=0;i<Integer.MAX_VALUE;i++){
executorService1.execute(new MyThread1());
}
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
}
}
通过指定JVM参数:-Xmx8m -Xms8m
运行以上代码,会抛出OOM:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
其实这里提示的就很明显的,主要是因为LinkedBlockingQueue出了问题,我们再来看一下底层:
Java中的BlockingQueue
主要有两种实现,分别是ArrayBlockingQueue
和 LinkedBlockingQueue
。
ArrayBlockingQueue
是有边界阻塞队列,对于LinkedBlockingQueue 来说如果没有指定大小的话将是一个无边界阻塞队列,而且里面的最大值如果在没有指定大小的情况下可以达到
Integer.MAX_VALUE,对于一个无边界队列来说,是可以不断的向队列中加入任务的,所以这个时候就会出现内存溢出的问题。
上面提到的问题主要体现在newFixedThreadPool
和newSingleThreadExecutor
两个工厂方法上,并不是说newCachedThreadPool
和newScheduledThreadPool
这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE
,而创建这么多线程,必然就有可能导致OOM。
阿里巴巴java开发手册上也明确说了尽量不用Executors创建线程池:
那既然问题找到了,我们解决问题就行啦~即对BlockingQueue设置初始值就行,这样一个无边界的阻塞队列就可以变成有边界的阻塞队列
@Test
public void test2(){
ExecutorService executorService1 = new ThreadPoolExecutor(10,30,100,TimeUnit.SECONDS
,new ArrayBlockingQueue<>(10),new ThreadPoolExecutor.CallerRunsPolicy();
for(int i=0;i<Integer.MAX_VALUE;i++){
executorService1.execute(new MyThread1());
}
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
}
}
这种情况下,一旦线程提交数超过我设定的数值,就会报一个异常`java.util.concurrent.RejectedExecutionException,这是因为当前队列已经是一个有边界的阻塞队列,一旦请求数量超过我设定的数值,就会抛出一个异常。
除了自己定义`ThreadPoolExecutor`外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。这里作者就不展开描述啦~感兴趣的小伙伴可以自己翻阅资料学习哦~