我们都知道在遇到一些单线程处理很慢的场景,往往我们会采用多线程的方式进行处理,从而缩短处理时间提升性能。
往往这个时候我们就会想到 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 问题,所以说有的时候方便的同时也到来了隐患,作为一个有追求的程序员,我们需要把一些细节的东西完全掌握住,这样在遇到才能在遇到问题的时候宠辱不惊。
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。