java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?
线程池技术是Java的一大特性,如果我们想要编写高并发、高吞吐的程序,线程池的技术使用是必须的。对于很多程序员来说,多线程和线程池技术都了然于胸,基本原理和使用都数量掌握,分分钟可以写出一个生产消费者模式的多线程,也能写出线程集合和线程相互等待的业务功能。但是,在使用多线程和线程池技术的过程中,我们会碰到各种各样诡异的问题,这种问题会莫名其妙的出现,我们很难定位。本文就示例说明关于阻塞线程的一大坑,各位Java程序员你是否踩过?我们应该如何避免呢?
四种线程池策略
Java的JUC包中有四种内置的创建线程池的策略,借助这些方策略,我们可以很方便的创建一个线程池对象在业务中使用。这四个策略分别为:
- 1、NewSingleThreadExecutor,单线程的线程池
单线程的线程池中只有一个线程,也就是所有任务都是通过这一个线程来串行执行,保证了执行任务按照提交顺序来执行。如果业务只是涉及到异步执行,可选择该线程池技术。
- 2、NewFixedThreadPool,固定数量线程的线程池
固定大小的线程池创建的时候限制了线程池中线程的数量。当有新的任务创建的时候,就创建一个新的线程来执行任务。当线程池中的线程数量达到设定的值的时候,任务开始等待执行,直到有可用的线程。
- 3、NewCachedThreadPool,可缓存的线程池
可缓存的线程池不限定线程数量,当有新的任务加入,如果没有可用的线程,那就创建新的线程。线程最大数量取决于操作系统所能创建的最大线程数量。此线程池技术也是项目开发中不被推荐的一种方法,因为如何控制不好任务数量,将会导致大量的线程被创建,影响系统的性能。
- 4、NewScheduledThreadPool,定时执行的线程池
定时执行的线程池支持创建无线数量的线程池,并且线程按照设定的时间周期执行。
这是四种线程池策略。我们可以通过JUC的相关API来创建相应的线程池,具体需要创建什么样的线程池策略,根据业务需求。如果只是让某个业务异步执行,那就使用NewSingleThreadExecutor,如果想定义固定线程数量那就使用NewFixedThreadPool,如果是启动定时任务来执行业务逻辑,那就使用NewScheduledThreadPool。
Executors导致的”大坑“
使用这四种线程池技术看似简单,通过相应的API就能快速创建。就是因为JUC API多于线程池技术的封装,导致我们在使用线程池技术会遇到一大坑。
Executors是JUC中一个核心的创建线程池的工具类,通过Executors我们可以快速的创建不同策略的线程池。Executors中的方法如下所示:
我们可以看到Executors类中提供了很多方法用来创建线程池。例如:我们要创建一个固定大小的线程池,示例代码如下:
那这段代码有没有什么问题呢?没有问题。但是,如果是在正常的业务流程中,就会有很大的问题,我们举一个实际的业务场景:
某个业务系统中,需要消费Kafka中的消息,然后将消息进行处理存库。Kafka消费消息的速度很快,但是入库的速度很慢,因为是消费和入库是同步执行的,导致Kafka的消息有挤压。所以我们需要将消费和入库分开异步执行,异步执行我们想到了使用线程池技术,我们可以开启固定数量线程的线程池技策略来多线程的入库。我们模拟一下场景代码:
通过测试发现,100次的循环很快结束,(即100条kafka消息很快消费完成)。所有的待处理任务全部加入了executorService的待处理队列任务中。如果循环1000次,executorService的队列中有大量的待处理任务,从而导致系统OOM。
通过研究源码我们可以发现,Executors.newFixedThreadPool(10)内部调用的ThreadPoolExecutor默认采用的阻塞队列是LinkedBlockingQueue,并且的它的长度是队列长度是Integer.MAX_VALUE,即2147483647。这就是导致堆积大量的请求,系统发生OOM的原因。
这就是用Executors创建线程池的一大坑,它隐藏了内部的实现细节。如果我们直接使用Executors来创建线程池,这对于高并发系统来说就是一场灾难。
ThreadPoolExecutor创建线程池
那回到上面的例子,我们如何解决这个问题呢?通过ThreadPoolExecutor直接来创建线程池,示例代码如下:
在ThreadPoolExecutor的构造方法中,我们可以定义阻塞队列的大小,这样就不会让线程池队列无限大而导致OOM了。但是,这里面又有一个坑:当阻塞队列满员,后面要再加入任务,会报错,如下错误所示:
ArrayBlockingQueue Offer方法的”坑“
通过研究源码我们发现,ThreadPoolExecutor中阻塞队列添加任务时采用的是offer方法,如下图所示:
我们都知道,LinkedBlockingQueue、ArrayBlockingQueue以及其他阻塞队列,offer方法在添加元素的时候,如果队列已满无法添加元素的时候,offer方法会直接返回false,这就会导致上面的异常。
自定义阻塞队列
那如果我们要实现一个阻塞线程池队列,我们改如何实现呢?自定义阻塞队列,覆写offer方法,示例代码如下:
自定义阻塞队列:
对于put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞,这正好满足我们的需求。改造后的代码如下:
通过运行我们发现满足我们的需求。
不会堆积大量的队列任务。
总结
Java线程池技术在业务中需要慎重使用,对于线程池技术的内部实现机制我们需要精通掌握,才能在实际项目中熟练使用。文中涉及到的”坑“大家还需要好好消化理解,在以后的项目开发中尽量避免。
总结
以上是生活随笔为你收集整理的java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 小程序云服务器选什么系统好,小程序云服务
- 下一篇: java 矩阵转置_图解利用Java实现