目录
Stream、ParallelStream和ForkjoinPool简述
Stream、ParallelStream和ForkjoinPool简述
stream是java8开始引入的流式编程
parallelStream是在流式编程的基础上并行化流水线上可以并行化的部分(StatelessOp)
fork-join-pool是parallelStream并行化的底层框架,是独立于ThreadPool的多线程并行模型。
三者虽然在功能上关系紧密,但掌握他们要了解的东西却互相独立
一 Stream的原理
二 ParallelStream的优势与坑点
parallelStream底层依赖ForkJoin并行框架。
forkjoin框架的特点是分治,把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果。这个结构和Stream流水线可以很好地结合。
默认的parallelStream底层依赖同一个线程池,默认线程数为机器CPU数量-1。(推荐独立的任务使用parallelStream时使用自定义的forkjoinpool,避免预期外的parallelStream对同一个底层线程池的影响。最佳实践会强调)。fork-join框架下,线程池相的线程数量对有限,但是几乎不限容量(2的64次方)的工作队列是根据分治算法分片分配的,上百万的子任务可以插入到工作队列,由线程池中的线程进行处理。为应对有的队列提前处理完成,fork-join中有工作窃取的处理机制。详见下图中参考文档。
图片来自。及fork-join框架可参考文章聊聊并发(八)——Fork/Join框架介绍-InfoQ
forkjoin框架中的任务队列参考
三 ParallelStream最佳实践
Java中自定义ForkJoinPool
使用的方式较为冗余丑陋,会影响代码的可读性;在非必要的情况下,尽量使用ThreadPool。
IO密集型操作,使用ThreadPool而不是Forkjoin、parallelStream。
避免在没有自定义ForkJoinPool
的情况下使用Collection.parallelStream()
或Stream.parallel()
。因为共用CommonPool
可能导致多个独立的业务操作之间相互争抢线程以至相互影响。优先避免使用,如果一定要用,请指定特定的ForkJoinPool。
JDK内置的CommonPool
内线程初始化时机可能非常早,相应线程上绑定的ClassLoader可能并非应用真正使用的ClassLoader,这对于依赖特定ClassLoader加载类的框架来说,可能出现意外的找不到类问题。
四 理解parallelStream执行的小实验
package hcity.concurrency;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.commons.lang3.RandomUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
public class ForkJoinTest {
ForkJoinPool forkJoinPool = new ForkJoinPool(3);
ForkJoinPool forkJoinPool2 = new ForkJoinPool(9);
Map<String, Integer> mem = new HashMap<>();
void test1() {
List<Integer> arrs = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println("=============== 实验1:核心线程为3的forkJoin执行情况。观察线程数和执行情况 ===============");
List<InnerTest01> ins = new ArrayList<>();
for (int i = 0; i < 10; i++) {
InnerTest01 test01 = new InnerTest01(i + "");
ins.add(test01);
}
long start = System.currentTimeMillis();
forkJoinPool.submit(() -> ins.parallelStream().forEach(this::addOp)).join();
System.out.println("耗时 : " + (System.currentTimeMillis() - start));
// ins.forEach(System.out::println);
System.out.println("=============== 实验1:结束 ===============\n");
// 根据自己喜好设定实验间隔时间。或者改写单独执行实验。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=============== 实验2:串行执行,比较耗时 ===============");
System.out.println("实验1中每个工作线程的总睡眠时间:" + mem); //
start = System.currentTimeMillis();
List<Integer> ans3 = ins.stream().map(this::addOp).collect(Collectors.toList());
System.out.println("耗时" + (System.currentTimeMillis() - start));
System.out.println(ans3);
System.out.println("=============== 实验2:结束 ===============\n");
System.out.println("=============== 实验3:两个自定义forkJoinPool运行。观察线程数和执行情况 ===============");
start = System.currentTimeMillis();
ForkJoinTask<List<Integer>> task1 = forkJoinPool.submit(() -> arrs.parallelStream().map(x -> addOp(x, forkJoinPool)).collect(Collectors.toList()));
ForkJoinTask<List<Integer>> task2 = forkJoinPool2.submit(() -> arrs.parallelStream().map(x -> addOp(x, forkJoinPool2)).collect(Collectors.toList()));
List<Integer> ans = task1.join();
List<Integer> ans2 = task2.join();
System.out.println("耗时" + (System.currentTimeMillis() - start));
System.out.println(ans);
System.out.println(ans2);
System.out.println("=============== 实验3:结束。 ===============");
}
private Integer addOp(int x, ForkJoinPool forkJoinPool) {
Thread t = Thread.currentThread();
String name = t.getName();
int stime = RandomUtils.nextInt(200, 1000);
System.out.println("示例1 Thread:" + name + " value:" + x + " sleep time " + stime + " activeCount :" + forkJoinPool.getActiveThreadCount() + " queueSize " + forkJoinPool.getQueuedTaskCount());
try {
Thread.sleep(stime);
} catch (InterruptedException e) {
e.printStackTrace();
}
return x + 1;
}
public static void main(String[] args) {
ForkJoinTest fj = new ForkJoinTest();
fj.test1();
}
@Getter
@Setter
@ToString
static
class InnerTest01 {
String a;
InnerTest01(String a) {
this.a = a;
}
}
int addOp(InnerTest01 test01) {
Thread t = Thread.currentThread();
String name = t.getName();
int stime = RandomUtils.nextInt(500, 1000);
System.out.println("示例0 Thread:" + name + " value:" + test01.getA() + " sleep time " + stime + " activeCount :" + forkJoinPool.getActiveThreadCount() + " queueSize " + forkJoinPool.getQueuedTaskCount());
mem.put(name, mem.getOrDefault(name, 0) + stime);
try {
Thread.sleep(stime);
} catch (InterruptedException e) {
e.printStackTrace();
}
int newA = Integer.parseInt(test01.getA()) + 1;
test01.setA(String.valueOf(newA));
return newA;
}
}
参考文档
深入理解Java8中Stream的实现原理_lcgoing的博客-CSDN博客_java stream流原理