【小家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了。但一般都是整数倍

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


相关文章
|
7天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
7天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
|
11天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
12天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
94 53
|
23天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
101 38
|
23天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
44 14
|
23天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
60 4
|
23天前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
94 2
|
25天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
26天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用