相关阅读
【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本
每篇一句
你如果不是富二代,你要努力让你的儿子成为富二代
说在前面
线程池关闭的意义不仅仅在于结束线程执行,避免内存溢出,因为大多使用的场景并非上述示例那样 朝生夕死。线程池一般是持续工作的全局场景,如数据库连接池。
我之前看到很多同事写代码,为了提高效率,采用多线程去优化。由为了提高多线程的性能,用到了线程池。
表面上看起来很高大上了,但其实上发现很多人用到了局部变量的线程池,然后使用过后并没有回收,导致了线程泄漏甚至内存溢出。
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秒就增长一个。
加了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) {
}
}
}
}
就一直很平稳
再看个例子
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了。但一般都是整数倍
若对于线程池的关闭有更多疑问,推荐博文:线程池的优雅关闭实践
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |