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

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

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

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

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

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

// 定时任务代码
@Configuration      // 1.用于标记配置类。
@EnableScheduling   // 2.开启定时任务
public class TasksSpringBoot {

    @Autowired
    private TestTransactionalService testTransactionalService;

    //3.添加定时任务 2秒执行一次
    @Scheduled(cron = "0/2 * * * * ?")
    // 直接指定时间间隔,例如:2秒
    //@Scheduled(fixedRate=2 * 1000)
    private void configureTasks() throws ExecutionException, InterruptedException {
        testTransactionalService.testTransactional();
        System.err.println("执行定时任务时间: " + LocalDateTime.now());
    }
}



// 业务代码
@Slf4j
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {


    public void testTransactional() throws ExecutionException, InterruptedException {
        int inter = 3;
        ExecutorService executorService = Executors.newFixedThreadPool(inter);
        ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executorService;
        FutureTask[] integerFuture = new FutureTask[inter];
        for (int i = 0; i < inter; i++) {
            int finalI = i + 1;
            integerFuture[i] = new FutureTask<>(() -> {
                return new TransactionCallback<String>() {
                    @Override
                    public String doInTransaction(TransactionStatus transactionStatus) {
                        System.out.println("多线程运行了---" + finalI);
                        return "业务执行成功";
                    }
                };
            });
            poolExecutor.execute(integerFuture[i]);
        }
        for (int i = 0; i < inter; i++) {
                System.out.println("integerFuture返回结果 = " + i + "==" + integerFuture[i].get());
        }

        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        System.out.println("线程总数为 = " + bean.getThreadCount());
    }
}


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

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

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

// 线程池管理类(饿汉模式加载)
public class CreateThreadPoolUtil {
    // 最原始的方式创建线程池
    private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            8, 32, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(128), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static ThreadPoolExecutor getInstance() {
        return poolExecutor;
    }
}




// 业务代码类
@Slf4j
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {

    public void testTransactional() throws ExecutionException, InterruptedException {
        int inter = 3;
        ThreadPoolExecutor poolExecutor = CreateThreadPoolUtil.getInstance();
        FutureTask[] integerFuture = new FutureTask[inter];
        for (int i = 0; i < inter; i++) {
            int finalI = i + 1;
            integerFuture[i] = new FutureTask<>(() -> {
                return new TransactionCallback<String>() {
                    @Override
                    public String doInTransaction(TransactionStatus transactionStatus) {
                        System.out.println("多线程运行了---" + finalI);
                        return "业务执行成功";
                    }
                };
            });
            poolExecutor.execute(integerFuture[i]);
        }
        for (int i = 0; i < inter; i++) {
                System.out.println("integerFuture返回结果 = " + i + "==" + integerFuture[i].get());
        }

        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        System.out.println("线程总数为 = " + bean.getThreadCount());

    }
}



// 配置定时任务类
@Configuration      // 1.用于标记配置类。
@EnableScheduling   // 2.开启定时任务
public class TasksSpringBoot {

    @Autowired
    private TestTransactionalService testTransactionalService;

    //3.添加定时任务 2秒执行一次
    @Scheduled(cron = "0/2 * * * * ?")
    // 直接指定时间间隔,例如:2秒
    //@Scheduled(fixedRate=2 * 1000)
    private void configureTasks() throws ExecutionException, InterruptedException {
        testTransactionalService.testTransactional();
        System.err.println("执行定时任务时间: " + LocalDateTime.now());
    }
}

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

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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐