正确创建线程池的方法
线程池可以通过Executors工具类自动创建,也可以手动创建。对于如何选择,《Java开发手册》有如下描述:
线程池不允许使用Executors创建,而是通过ThreadPoolExecutor方式。这样可以使开发人员更加明确线程池的运行规则,规避资源耗尽的风险。
Executros生成线程池对象的弊端:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能堆积大量请求,导致OOM。
- CachedThreadPool允许创建的线程数量为Integer.MAX_VALUE,可能创建大量的线程,导致OOM。
FixedThreadPool内存溢出的示例
FixedThreadPool采用无界队列,容量上限是Integer.MAX_VALUE,当请求越来越多,并且无法及时处理完毕时,就会占用大量内存,导致OOM。
public class FixedThreadPoolOOM {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
for(int i = 0; i < Integer.MAX_VALUE; i++) {
System.out.println(i);
pool.execute(new Task());
}
}
}
class Task implements Runnable{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
为了能够尽快内存溢出,调整JVM参数如下
-Xms8M -Xmx8M
运行结果:内存溢出时,工作队列中有164963个任务。
164963
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:403)
at java.lang.String.valueOf(String.java:3099)
at java.io.PrintStream.print(PrintStream.java:597)
at java.io.PrintStream.println(PrintStream.java:736)
at com.lzp.java.concurrent.threadpool.FixedThreadPoolOOM.main(FixedThreadPoolOOM.java:19)
核心线程数的计算
根据不同的业务场景,自己设置线程池参数,例如内存多大,线程名字等。
- CPU密集型任务(加密、计算hash等):最佳线程数为CPU核心数的1-2倍。
- 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般大于CPU核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上。估算公式为
线程数 = CPU核心数 * (1 + 平均等待时间 / 平均工作时间)
如果需要更加精准的数值,则需要进行压测。