Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:714)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:950)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1368)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.wenniuwuren.concurrent.newCachedThreadPoolTest.main(newCachedThreadPoolTest.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
业务开发过程中, 比如说我们要实现一个功能, 但是该功能需要调用其他系统的多个接口做数据组装, 多个被调用接口之间无依赖关系, 按照正常逻辑, 我们可以串行依次调用多个接口, 假如有三个接口, 我们的代码如下:
//伪代码 伪代码 伪代码! 重要的事情说三遍
public Result demo(){
//调用第一个接口, 耗时1s
Result interface1Result = interface1();
//调用第二个接口, 耗时2s
Result interface2Result = interface2();
//调用第三个接口, 耗时3s
Result interface3Result = interface3();
//组装数据
Result result = interface1Result + interface2Result +interface3Result;
return result;
}
满足业务需要,没有问题, 当我们考虑性能的时候, 这个接口的最小耗时等于三个接口的耗时之和, 也就是6s, 针对这种情况, 我们想当然的觉得, 可以把串行的接口调用改成并行, 这样的话, 接口响应时间应该提升为3s, 伪代码入下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//伪代码 伪代码 伪代码! 重要的事情说三遍
public Result demo(){
//调用第一个接口, 耗时1s
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Result interface1Result = interface1();
}
});
}
//调用第二个接口, 耗时2s
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Result interface2Result = interface2();
}
});
}
//调用第三个接口, 耗时3s
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Result interface3Result = interface3();
}
});
}
//组装数据
Result result = interface1Result + interface2Result +interface3Result;
return result;
}
这种情况下, 我们使用线程池来并行调用三个接口, 最终响应时间得到了提升, 但是单机的tps在压测的时候, 就会出现我们文章标题的错误, 因为每个方法需要3s的处理时长, 当压力不断增加的时候, 线程池没有可用的线程时, 会一直新建线程, 当超出jvm的栈内存大小时, 就会报出无法再创建线程的错误.
这种情况下, 我们可以使用定长的线程池, 比如估计一个线程池大小, 100, 当无线程可用的时候, 我们就阻塞等待, 这种情况比较稳, 但需要能够比较准确的估算线程池大小, 要不然服务器资源会有一定的浪费, 这不符合我们勤俭持家程序员的风格, 于是下面我们介绍一下, 如何准确的估算线程池的大小:
引自:《Java Concurrency in Practice》即《java并发编程实践》
如上图,在《Java Concurrency in Practice》一书中,给出了估算线程池大小的公式:
Nthreads=Ncpu*Ucpu*(1+w/c),其中
Ncpu=CPU核心数
Ucpu=cpu使用率,0~1
W/C=等待时间与计算时间的比率
比如说, 服务器cpu为32核, 一般cpu使用到80%会引起系统告警, 等待时间估计为 0.2s, 计算时间为 0.1s,
针对这种情况:
Nthreads=32*0.8*(1+0.2/0.1) = 76.8
所以我们就设置线程池大小为 75 就ok了