Java 中 再一个你可能不经常用,但使用的时候不一定会注意的一个知识点

我们都知道在遇到一些单线程处理很慢的场景,往往我们会采用多线程的方式进行处理,从而缩短处理时间提升性能。

往往这个时候我们就会想到 Java JUC 包中为我们提供的线程池创建方法,如下所示,通过 JDK 自带的 Executors 类中的几个静态方法,创建我们需要的线程池,再通过 ExecutorService 的带返回值的 submit() 或者 不带返回值的 execute() 方式来异步处理业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();

1
` public static ExecutorService newSingleThreadExecutor() {         return new FinalizableDelegatedExecutorService             (new ThreadPoolExecutor(1, 1,                                     0L, TimeUnit.MILLISECONDS,                                     new LinkedBlockingQueue<Runnable>()));     }  `

缓存线程: Executors.newCachedThreadPool();

1
` public static ExecutorService newCachedThreadPool() {         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                       60L, TimeUnit.SECONDS,                                       new SynchronousQueue<Runnable>());     }  `

固定线程Executors.newFixedThreadPool(2);

1
` public static ExecutorService newFixedThreadPool(int nThreads) {         return new ThreadPoolExecutor(nThreads, nThreads,                                       0L, TimeUnit.MILLISECONDS,                                       new LinkedBlockingQueue<Runnable>());     }  `

定时线程: Executors.newScheduledThreadPool(3);(父类中)

1
` 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 问题,所以说有的时候方便的同时也到来了隐患,作为一个有追求的程序员,我们需要把一些细节的东西完全掌握住,这样在遇到才能在遇到问题的时候宠辱不惊。

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。