【小家Java】自定义的线程池需要关闭吗?(局部变量Executors线程池一定要手动关闭)

简介: 【小家Java】自定义的线程池需要关闭吗?(局部变量Executors线程池一定要手动关闭)

说在前面


线程池关闭的意义不仅仅在于结束线程执行,避免内存溢出,因为大多使用的场景并非上述示例那样 朝生夕死。线程池一般是持续工作的全局场景,如数据库连接池。


我之前看到很多同事写代码,为了提高效率,采用多线程去优化。由为了提高多线程的性能,用到了线程池。


表面上看起来很高大上了,但其实上发现很多人用到了局部变量的线程池,然后使用过后并没有回收,导致了线程泄漏甚至内存溢出。


Executors作为局部变量时,创建了线程,一定要记得调用executor.shutdown();来关闭线程池,如果不关闭,会有线程泄漏问题。


实例模拟


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThread {
    public static void main(String[] args) {
        while (true) {
            try {
                ExecutorService service = Executors.newFixedThreadPool(1);
                service.submit(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(2000); 模拟处理业务
                        } catch (InterruptedException e) {
                        }
                    }
                });
                service = null;
            } catch (Exception e) {
            }
            try {
                Thread.sleep(2000); 
            } catch (InterruptedException e) {
            }
        }
    }
}

运行后,查看jvm,会发现线程每2秒就增长一个。


image.png


加了shutdown代码后


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThread {
    public static void main(String[] args) {
        while (true) {
            ExecutorService service = Executors.newFixedThreadPool(1);
            try {
                service.submit(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                        }
                    }
                });
            } catch (Exception e) {
            }finally{
                service.shutdown();
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
        }
    }
} 


就一直很平稳


image.png


再看个例子


public static void main(String[] args) throws Exception {        //用于获取到本java进程,进而获取总线程数
    RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
    String jvmName = runtimeBean.getName();
    System.out.println("JVM Name = " + jvmName);
    long pid = Long.valueOf(jvmName.split("@")[0]);
    System.out.println("JVM PID  = " + pid);
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    int n = 1000;
    for (int i = 0; i < n; i++) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 1000, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        for (int j = 0; j < 5; j++) {
            executor.execute(() -> {
                System.out.println("当前线程总数为:" + bean.getThreadCount());
            });
        }
         // executor.shutdown();
    }
    Thread.sleep(10000);
    System.out.println("线程总数为 = " + bean.getThreadCount());
}

这里ThreadPoolExecutor作为局部变量,若你不手动关闭:最后一句输出为



线程总数为 = 5006

也就是说:线程全部泄漏(一个线程都没有死,没有被回收),白白的浪费了内存。这个在内存吃紧的时候容易造成死机。


那么加上executor.shutdown()这句,手动去给它关闭呢?最终打印:


线程总数为 = 6


可见,效果是非常好的。因此:局部线程池,请务必务必要手动关闭。


注意:还有个区别是若你没有shutdonw,那么最终主线程是不会终止的。而如果你shutdown了,主线程跑完也就终止了。


最后说明


此处用的newFixedThreadPool(1)来模拟业务创建,但是勿喷。实际情况中一般不会创建只有一个线程的线程池,这里只是表达一个意思即可。


希望大家能够举一反三。


线程池设置多大合适呢


虽然线程池大小的设置受到很多因素影响,但是这里给出一个参考公式:


最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目


比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:


最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目


线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。


所以并不是单纯的只是配一个CUP核心数就ok了。但一般都是整数倍

若对于线程池的关闭有更多疑问,推荐博文:线程池的优雅关闭实践


相关文章
|
2月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
185 2
|
7月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
281 0
|
10月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
479 60
【Java并发】【线程池】带你从0-1入门线程池
|
11月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
8月前
|
Java
线程池是什么?线程池在实际工作中的应用
总的来说,线程池是一种有效的多线程处理方式,它可以提高系统的性能和稳定性。在实际工作中,我们需要根据任务的特性和系统的硬件能力来合理设置线程池的大小,以达到最佳的效果。
247 18
|
9月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
486 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
10月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
11月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
405 17
|
2月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
166 6
|
5月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
309 83