我们都知道在遇到一些单线程处理很慢的场景,往往我们会采用多线程的方式进行处理,从而缩短处理时间提升性能。
往往这个时候我们就会想到 Java
中JUC
包中为我们提供的线程池创建方法,如下所示,通过 JDK
自带的 Executors
类中的几个静态方法,创建我们需要的线程池,再通过 ExecutorService
的带返回值的 submit()
或者 不带返回值的 execute()
方式来异步处理业务逻辑。
ExecutorService executorService0 = Executors.newFixedThreadPool(10);
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newCachedThreadPool();
Future<?> submit = executorService0.submit(new Runnable() {
@Override
public void run() {
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
}
});
代码写到了这里,普通程序员都觉得应该已经结束了,剩下的就交给线程池就好了,不过稍微优秀一点的程序员会觉得有点不妥,知道应该再根据我们业务逻辑情况以及服务器的配置,进行线程数的配置,设置合理的线程数。比如这里设置的是 10,我们可以根据自身的情况进行相应的配置。
大部分程序员到这一步就真的觉得应该结束了,该设置的也设置了,接下来就让线程池按照我们的配置好好运行,一切都很完美。
然后事情并没有想象的那么美好,有时候发现在高峰期的时候,偶尔会 OOM 内存溢出的情况,那为什么我们这个逻辑会出现 OOM 呢?
带着这个问题,我们来研究一下,只要看过线程池源码的同学都知道,不管是 Executors.newFixedThreadPool()
,Executors.newSingleThreadExecutor()
还是 Executors.newCachedThreadPool()
,底层的实现都是通过构造 ThreadPoolExecutor
这个类来实现了,不同的地方只是具体参数的不同而已。如下所示
单个线程: Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
缓存线程: Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
固定线程Executors.newFixedThreadPool(2);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
定时线程: Executors.newScheduledThreadPool(3);
(父类中)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
通过这几个静态方法对应的构造实现,我们可以发现 newSingleThreadExecutor()
和 newFixedThreadPool()
中的消息队列的长度为 Integer.MAX_VALUE
,而 newCachedThreadPool()
方法允许创建的最大线程数为 Integer.MAX_VALUE
。
看到这里的小伙伴估计知道我想说什么了,是的,这些固定的静态方法的默认配置有很大的问题!
队列长度太长在高峰期的时候会堆积大量的请求,从而产生 OOM,而创建大量的线程也是一个道理,会把服务器的资源消耗殆尽,从而也产生 OOM!
这一点在阿里巴巴的开发手册中也有明确的说名,如下图所示,可见阿里巴巴手册中的每个点,都值得我们深入的进行钻研!
所以针对这种情况,不建议采用JDK
自带的 Executors
的静态方法,而是通过 ThreadPoolExecutor
类自己来构造线程池,这样每个参数我们都可以根据情况进行自定义。关于 ThreadPoolExecutor
的七个参数的定义如下:
corePoolSize
: 核心线程数的大小maximumPoolSize
: 线程池中允许的最大线程数keepAliveTime
: 空闲线程允许的最大的存活时间unit
: 存活时间的单位workQueue
: 阻塞任务队列threadFactory
: 线程工厂用来创建线程handler
: 拒绝策略,针对当队列满了时新来任务的处理方式
通过自定义构造函数的具体参数,我们就可以避免上面说到的 OOM 问题,所以说有的时候方便的同时也到来了隐患,作为一个有追求的程序员,我们需要把一些细节的东西完全掌握住,这样在遇到才能在遇到问题的时候宠辱不惊。