最近做压测中发现一个应用中cpu过高,导致接口超时rt情况有些不大稳定,jstack打印线程一直在parallelStream相关的代码出进行计算。故对parallelStream相关做一下研究,找一下优化方法。
java8并行流parallelStream,相信很多人都喜欢用,特别方便简单。但是有多少人真正知道里面采用的共享线程池对密集型任务,高并发下的性能影响呢
可能你的一个应用里面,有几个密集型的计算任务,但是相信你的应用里面还有更多的非密集型的任务吧。如果密集型的任务一直占用着线程资源,那么对你系统造成的压力会有多少?
下面直接贴代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
/**
* created by devin on 2019/9/30
*/
public class ForkJoinPoolTest {
//最大可用的CPU核数
public static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
public static void main(String[] args) throws InterruptedException {
//定义初始的2个数组
List<Integer> firstRange = new ArrayList<>();
List<Integer> secondRange = new ArrayList<>();
for (int i = 0; i < 10; i++) {
firstRange.add(i);
secondRange.add(i);
}
System.out.println(ForkJoinPool.getCommonPoolParallelism());
//注意查看线程sleep时候,有几个线程在打印数据。特意sleep时间间隔较长
test1(firstRange, secondRange);
// test2(firstRange, secondRange);
//保证程序一直存活的,打印数据
while (true) {
System.out.println(Thread.currentThread().getName());
Thread.sleep(500000);
System.out.println();
}
}
//使用java8 parallelStream共享缓存
public static void test1(List<Integer> firstRange, List<Integer> secondRange) {
Runnable firstTask = () -> {
firstRange.parallelStream().forEach((number) -> {
try {
// do something slow
System.out.println("test1.1 -- >" + Thread.currentThread().getName() + " num=" + number);
Thread.sleep(50000);//间歇50s是为了方便看一次执行的线程数量
} catch (InterruptedException e) {
}
});
};
Runnable secondTask = () -> {
secondRange.parallelStream().forEach((number) -> {
try {
// do something slow
System.out.println("test1.2 -- >" + Thread.currentThread().getName() + " num=" + number);
Thread.sleep(50000);
} catch (InterruptedException e) {
}
});
};
//new一个线程,模拟不同的请求,以免认为是顺序调用
new Thread(new Runnable() {
@Override
public void run() {
firstTask.run();
}
},"thread-111111").start();
//new一个线程,模拟不同的请求,以免认为是顺序调用
new Thread(new Runnable() {
@Override
public void run() {
secondTask.run();
}
}, "thread-222222").start();
}
//包装线程池,不使用共享线程池,因为不是每个线程都必须是计算密集型的
public static void test2(List<Integer> firstRange, List<Integer> secondRange) {
//线程池大小可以根据cpu个数来定义
ForkJoinPool forkJoinPool = new ForkJoinPool(PROCESSORS);
forkJoinPool.submit(() -> {
firstRange.parallelStream().forEach((number) -> {
try {
System.out.println("test2.1 -- >" + Thread.currentThread().getName() + " num=" + number);
Thread.sleep(50000);
} catch (InterruptedException e) {
}
});
});
ForkJoinPool forkJoinPool2 = new ForkJoinPool(PROCESSORS);
forkJoinPool2.submit(() -> {
secondRange.parallelStream().forEach((number) -> {
try {
System.out.println("test2.2 -- >" + Thread.currentThread().getName() + " num=" + number);
Thread.sleep(50000);
} catch (InterruptedException e) {
}
});
});
}
}
我们直接来看看test1的执行结果。sleep时间较长,是为了更好地结果输出
我们会发现,共享线程池,每次只有三个共享线程执行了任务,如果你一个应用都使用到的是共享线程池,而其中有的是rpc调用呢,线程是会被一直占用资源的,会一定程度的降低应用的整体性能。
而test2是我们对该种情况的一个优化,我们如果对其进行包装,用用线程池包装,就会解决这个问题。
那么你的一个应用中就不会因为共享线程的阻塞,或者等待rpc请求调用而导致共享的线程无法释放,而导致应用整体性能降低的问题。
================================
另外,细心的同学会发现,为什么共享的线程是3个?这个是系统默认的个数:Runtime.getRuntime().availableProcessors()-1个(ForkJoinPool.getCommonPoolParallelism())。测试电脑是4core的,所以是3个线程。
以下是源码:
另外对于密集型应用该设置多大线程池呢?有的文章介绍是(cpu核数 +1)。非计算密集型的 2 * cpu核数。供参考