• 线程池需要关闭吗?使用线程池出现内存泄露的详细分析


    我们使用线程池后会关闭吗? 线程池在项目中是需要持续工作的全局场景,不建议手动关闭线程池(具体结合自己的项目场景)。

    现象:最近项目遇到一个问题,项目中有个定时任务微服务,里面有个定时任务需要没分钟执行一次。项目测试环境运行2天后,项目挂了。经过查看日志发现出现了java.lang.OutOfMemoryError: Java heap space  发生了内存泄露。

    问题原因:经过排查发现是线程池反复创建,线程数量不断上升,导致的内存泄露(内存泄露的原因有很多,这只是是其中一种)。由于公司代码保密,现在我自己写个demo来展示。

    思考:我们发现是反复创建线程池导致的,难道我们要shutdown()关闭吗?答案是:不能直接关闭,我们要想其他的办法。

    为什么不能直接关闭:我们创建线程池的目的就是反复利用线程池里的线程,如果频繁创建和关闭线程池,解决了内存泄露的问题,但是失去了使用线程池的意义。我们要如何解决呢?请看下面的分析及解决方案。

    模拟出现内存泄露的代码:使用定时任务每2秒钟执行一次。

    1. // 定时任务代码
    2. @Configuration // 1.用于标记配置类。
    3. @EnableScheduling // 2.开启定时任务
    4. public class TasksSpringBoot {
    5. @Autowired
    6. private TestTransactionalService testTransactionalService;
    7. //3.添加定时任务 2秒执行一次
    8. @Scheduled(cron = "0/2 * * * * ?")
    9. // 直接指定时间间隔,例如:2秒
    10. //@Scheduled(fixedRate=2 * 1000)
    11. private void configureTasks() throws ExecutionException, InterruptedException {
    12. testTransactionalService.testTransactional();
    13. System.err.println("执行定时任务时间: " + LocalDateTime.now());
    14. }
    15. }
    16. // 业务代码
    17. @Slf4j
    18. @Service
    19. public class TestTransactionalServiceImpl implements TestTransactionalService {
    20. public void testTransactional() throws ExecutionException, InterruptedException {
    21. int inter = 3;
    22. ExecutorService executorService = Executors.newFixedThreadPool(inter);
    23. ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executorService;
    24. FutureTask[] integerFuture = new FutureTask[inter];
    25. for (int i = 0; i < inter; i++) {
    26. int finalI = i + 1;
    27. integerFuture[i] = new FutureTask<>(() -> {
    28. return new TransactionCallback() {
    29. @Override
    30. public String doInTransaction(TransactionStatus transactionStatus) {
    31. System.out.println("多线程运行了---" + finalI);
    32. return "业务执行成功";
    33. }
    34. };
    35. });
    36. poolExecutor.execute(integerFuture[i]);
    37. }
    38. for (int i = 0; i < inter; i++) {
    39. System.out.println("integerFuture返回结果 = " + i + "==" + integerFuture[i].get());
    40. }
    41. ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    42. System.out.println("线程总数为 = " + bean.getThreadCount());
    43. }
    44. }

    控制台打印,线程总数不断上升

    解决方案:创建静态线程池,项目启动的时候就加载线程池,只创建一次。代码展示如下:

    创建线程池方式的详解可以看下这篇文章:多线程--线程和线程池的用法_傻鱼爱编程的博客-CSDN博客

    1. // 线程池管理类(饿汉模式加载)
    2. public class CreateThreadPoolUtil {
    3. // 最原始的方式创建线程池
    4. private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
    5. 8, 32, 60L, TimeUnit.SECONDS,
    6. new LinkedBlockingQueue<>(128), Executors.defaultThreadFactory(),
    7. new ThreadPoolExecutor.AbortPolicy());
    8. public static ThreadPoolExecutor getInstance() {
    9. return poolExecutor;
    10. }
    11. }
    12. // 业务代码类
    13. @Slf4j
    14. @Service
    15. public class TestTransactionalServiceImpl implements TestTransactionalService {
    16. public void testTransactional() throws ExecutionException, InterruptedException {
    17. int inter = 3;
    18. ThreadPoolExecutor poolExecutor = CreateThreadPoolUtil.getInstance();
    19. FutureTask[] integerFuture = new FutureTask[inter];
    20. for (int i = 0; i < inter; i++) {
    21. int finalI = i + 1;
    22. integerFuture[i] = new FutureTask<>(() -> {
    23. return new TransactionCallback() {
    24. @Override
    25. public String doInTransaction(TransactionStatus transactionStatus) {
    26. System.out.println("多线程运行了---" + finalI);
    27. return "业务执行成功";
    28. }
    29. };
    30. });
    31. poolExecutor.execute(integerFuture[i]);
    32. }
    33. for (int i = 0; i < inter; i++) {
    34. System.out.println("integerFuture返回结果 = " + i + "==" + integerFuture[i].get());
    35. }
    36. ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    37. System.out.println("线程总数为 = " + bean.getThreadCount());
    38. }
    39. }
    40. // 配置定时任务类
    41. @Configuration // 1.用于标记配置类。
    42. @EnableScheduling // 2.开启定时任务
    43. public class TasksSpringBoot {
    44. @Autowired
    45. private TestTransactionalService testTransactionalService;
    46. //3.添加定时任务 2秒执行一次
    47. @Scheduled(cron = "0/2 * * * * ?")
    48. // 直接指定时间间隔,例如:2秒
    49. //@Scheduled(fixedRate=2 * 1000)
    50. private void configureTasks() throws ExecutionException, InterruptedException {
    51. testTransactionalService.testTransactional();
    52. System.err.println("执行定时任务时间: " + LocalDateTime.now());
    53. }
    54. }

    控制台打印,线程总数基本稳定:

     总结:我们使用线程池就是为了反复利用线程的,所以在真正的项目中不会出现手动关闭线程池的操作。要尽量想其他的方案。这样使用线程池才有意义。

  • 相关阅读:
    Kubernetes CoreDNS 详解
    了解结构体在编程中的应用与用法
    动态规划算法(3)(不同方案数问题+拆分问题)
    Hadoop笔记
    springboot+Mybatis项目初始化
    Tomcat服务部署、优化
    SpringMVC简单介绍与使用
    网络框架重构之路plain2.0(c++23 without module) 环境
    解决XshellL/SecureCRT使用rz指令上传文件乱码且文件无法删除的问题
    java面向对象设计的五大原则
  • 原文地址:https://blog.csdn.net/m0_57640408/article/details/126088386