共计 3046 个字符,预计需要花费 8 分钟才能阅读完成。
在 Java 语言中,并发编程都是依附线程池实现的,而线程池的创立形式又有很多,但从大的分类来说,线程池的创立总共分为两大类:手动形式应用 ThreadPoolExecutor 创立线程池和应用 Executors 执行器主动创立线程池。
那到底要应用哪种形式来创立线程池呢?咱们明天就来具体的聊一聊。
先说论断
在 Java 语言中,肯定要应用 ThreadPoolExecutor 手动的形式来创立线程池,因为这种形式能够通过参数来管制最大工作数和回绝策略,让线程池的执行更加通明和可控,并且能够躲避资源耗尽的危险。
OOM 危险演示
如果咱们应用了 Executors 执行器主动创立线程池的形式来创立线程池,那么就会存现线程溢出的危险,以 CachedThreadPool 为例咱们来演示一下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorExample {
static class OOMClass {
// 创立 1MB 大小的变量(1M = 1024KB = 1024*1024Byte)private byte[] data_byte = new byte[1 * 1024 * 1024];
}
public static void main(String[] args) throws InterruptedException {
// 应用执行器主动创立线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
List<Object> list = new ArrayList<>();
// 增加工作
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
// 定时增加
try {Thread.sleep(finalI * 200);
} catch (InterruptedException e) {e.printStackTrace();
}
// 将 1M 对象增加到汇合
OOMClass oomClass = new OOMClass();
list.add(oomClass);
System.out.println("执行工作:" + finalI);
}
});
}
}
}
第 2 步将 Idea 中 JVM 最大运行内存设置为 10M(设置此值次要是为了不便演示),如下图所示:
以上程序的执行后果如下图所示:
从上述后果能够看出,当线程执行了 7 次之后就开始呈现 OutOfMemoryError 内存溢出的异样了。
内存溢出起因剖析
想要理解内存溢出的起因,咱们须要查看 CachedThreadPool 实现的细节,它的源码如下图所示:
构造函数的第 2 个参数被设置成了 Integer.MAX_VALUE,这个参数的含意是最大线程数,所以因为 CachedThreadPool 并不限度线程的数量,当工作数量特地多的时候,就会创立十分多的线程。而下面的 OOM 示例,每个线程至多要耗费 1M 大小的内存,加上 JDK 零碎类的加载也要占用一部分的内存,所以当总的运行内存大于 10M 的时候,就呈现内存溢出的问题了。
应用 ThreadPoolExecutor 来改良
接下来咱们应用 ThreadPoolExecutor 来改良一下 OOM 的问题,咱们应用 ThreadPoolExecutor 手动创立线程池的形式,创立一个最大线程数为 2,最多可存储 2 个工作的线程池,并且设置线程池的回绝策略为疏忽新工作,这样就能保障线程池的运行内存大小不会超过 10M 了,实现代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* ThreadPoolExecutor 演示示例
*/
public class ThreadPoolExecutorExample {
static class OOMClass {
// 创立 1MB 大小的变量(1M = 1024KB = 1024*1024Byte)private byte[] data_byte = new byte[1 * 1024 * 1024];
}
public static void main(String[] args) throws InterruptedException {
// 手动创立线程池,最大线程数 2,最多存储 2 个工作,其余工作会被疏忽
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2,
0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy()); // 回绝策略:疏忽工作
List<Object> list = new ArrayList<>();
// 增加工作
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
// 定时增加
try {Thread.sleep(finalI * 200);
} catch (InterruptedException e) {e.printStackTrace();
}
// 将 1m 对象增加到汇合
OOMClass oomClass = new OOMClass();
list.add(oomClass);
System.out.println("执行工作:" + finalI);
}
});
}
// 敞开线程池
threadPool.shutdown();
// 检测线程池的工作执行完
while (!threadPool.awaitTermination(3, TimeUnit.SECONDS)) {System.out.println("线程池中还有工作在解决");
}
}
}
以上程序的执行后果如下图所示:
从上述后果能够看出,线程池从开始执行到执行完结都没有呈现 OOM 的异样,这就是手动创立线程池的劣势。
其余创立线程池的问题
除了 CachedThreadPool 线程池之外,其余应用 Executors 主动创立线程池的形式,也存在着其余一些问题,比方 FixedThreadPool 它的实现源码如下:
而默认状况下工作队列 LinkedBlockingQueue 的存储容量是 Integer.MAX_VALUE,也是趋向于无限大,如下图所示:
这样就也会造成,因为线程池的工作过多而导致的内存溢出问题。其余几个应用 Executors 主动创立线程池的形式也存在此问题,这里就不一一演示了。
总结
线程池的创立形式总共分为两大类:手动应用 ThreadPoolExecutor 创立线程池和主动应用 Executors 执行器创立线程池的形式。其中 应用 Executors 主动创立线程的形式,因为线程个数或者工作个数不可控,可能会导致内存溢出的危险,所以在创立线程池时,倡议应用 ThreadPoolExecutor 的形式来创立。
是非审之于己,毁誉听之于人,得失安之于数。
公众号:Java 面试真题解析
面试合集:https://gitee.com/mydb/interview