6.1 并行流
6.1.1 将顺序流转换成并行流
6.1.2 测试流性能
package com.java.lamdba.six;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public class Demo {
public static long measureSumPerf(Function<Long, Long> adder, long n) {
long fastest = Long.MAX_VALUE;
for (int i = 0; i < 10; i++) {
long start = System.nanoTime();
long sum = adder.apply(n);
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Result: " + sum);
if (duration < fastest){
fastest = duration;
}
}
return fastest;
}
public static void main(String[] args) {
// 累加数据
long sum = IntStream.iterate(1, i -> i+1).limit(10).sum();
// long sum1 = Stream.iterate(1L, i -> i+1).limit(10).parallel().reduce(0L,Long::sum);
// System.out.println(sum);
// 并行流性能比较
System.out.println("Sequential sum done in:" +
measureSumPerf(Demo::parallelrangedSum, 10_000_000) + " msecs");
}
/** 顺序流累加
* @param n
* @return
*/
public static long sequentialSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.reduce(0L, Long::sum);
}
/** 并行流累加
* @param n
* @return
*/
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel()
.reduce(0L, Long::sum);
}
/** 并行流累加(避免拆箱)
* @param n
* @return
*/
public static long parallelrangedSum(long n) {
return LongStream.rangeClosed(1L, n)
.parallel()
.reduce(0L, Long::sum);
}
/** 避免拆箱的操作
* @param n
* @return
*/
public static long rangedSum(long n) {
return LongStream.rangeClosed(1, n)
.reduce(0L, Long::sum);
}
/** 迭代器累加
* @param n
* @return
*/
public static Long iterativeSum(long n) {
long result = 0;
for (long i = 1L; i <= n; i++) {
result += i;
}
return result;
}
}
6.1.3 正确使用并行流
/** 演示错误的并行流
* @param n
* @return
*/
public static long sideEffectSum(long n) {
Accumulator accumulator = new Accumulator();
LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);
return accumulator.total;
}
6.1.4 高效使用并行流
重点
6.2 分支/合并框架
6.2.1 使用RecursiveTask
要想把任务提交到池,必须创建RecursiveTask的一个子类,其中R是并行化任务(或者子任务)产生的结果类型。或者如果任务不返回结果,则没有泛型,并且要实现实现抽象方法compute
这个方法同时定义了将任务拆成子任务的逻辑,以及无法拆分时,单个任务的逻辑
这里有点像分治算法,下面我们以一个数字范围long数组表示求和.
package com.java.lamdba.six;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class ForkJoinSumCalculator extends RecursiveTask<Long> {
private long[] numbers;
// 子任务的起始位置和终止位置
private int start;
private int end;
/**
* 不再分割的数组大小
*/
private final long THRESHOLD = 10_000;
@Override
protected Long compute() {
int length = end - start;
// 如果大小小于等于阈值,顺序执行
if (length <= THRESHOLD) {
return computeSequentially();
}
// 创建一个子任务为数组的前一半求和
ForkJoinSumCalculator leftTask =
new ForkJoinSumCalculator(numbers, start, start + length/2);
// 利用ForkJoinTask线程异步执行新创建的子任务
leftTask.fork();
// 创建一个子任务为数组的后一半求和
ForkJoinSumCalculator rightTask =
new ForkJoinSumCalculator(numbers, start + length/2, end);
// 同步执行第二个子任务,有可能允许进一步递归划分
Long rightResult = rightTask.compute();
// 读取第一个子任务的结果,如果尚未完成就等待
Long leftResult = leftTask.join();
// 拼接来两个结果
return leftResult + rightResult;
}
private long computeSequentially() {
long sum = 0;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
public ForkJoinSumCalculator(long[] numbers) {
this(numbers, 0, numbers.length);
}
private ForkJoinSumCalculator(long[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
public static long forkJoinSum(long n) {
long[] numbers = LongStream.rangeClosed(1, n).toArray();
ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
return new ForkJoinPool().invoke(task);
}
}
6.2.2 使用分支/合并框架最佳做法
6.2.3 工作窃取
6.3 Spliterator
6.3.1 拆分过程
6.3.2 实现你自己的Spliterator
package com.java.lamdba.six;
import lombok.Data;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* 计算句子单词的个数
*/
@Data
public class WordCounter {
public WordCounter() {
}
public static void main(String[] args) {
final String SENTENCE =
" Nel mezzo del cammin di nostra vita " +
"mi ritrovai in una selva oscura" +
" ché la dritta via era smarrita ";
Stream<Character> stream = IntStream.range(0, SENTENCE.length())
.mapToObj(SENTENCE::charAt);
System.out.println("Found " + new WordCounter().countWords(stream) + " words");
}
private int count;
private boolean lastSpace;
public static int countWordsIteratively(String s) {
int counter = 0;
boolean lastSpace = true;
for (char c : s.toCharArray()) {
// 如果是空格,先将空格状态打开
if (Character.isWhitespace(c)) {
lastSpace = true;
} else {
if (lastSpace) {
counter++;
}
lastSpace = false;
}
}
return counter;
}
public WordCounter(int count, boolean lastSpace) {
this.count = count;
this.lastSpace = lastSpace;
}
/** 和countWordsIteratively 逻辑一样
* @param character
* @return
*/
public WordCounter accmulate(Character character){
if (Character.isWhitespace(character)){
return lastSpace ? this:new WordCounter(count,true);
}else {
return lastSpace ? new WordCounter(count + 1,false) : this;
}
}
// 合并两个WordCounter 把其累加起来 仅需要合并总和,无须关系空格状态
public WordCounter combine(WordCounter wordCounter) {
return new WordCounter(count + wordCounter.count,
wordCounter.lastSpace);
}
private int countWords(Stream<Character> stream) {
// 使用流接收数据
WordCounter wordCounter = stream.reduce(new WordCounter(0, true),
WordCounter::accmulate,
WordCounter::combine);
return wordCounter.getCount();
}
}
package com.java.lamdba.six;
import java.util.Spliterator;
import java.util.function.Consumer;
class WordCounterSpliterator implements Spliterator<Character> {
private final String string;
private int currentChar = 0;
public WordCounterSpliterator(String string) {
this.string = string;
}
// 处理当当前字符,如果还有字符要返回的就返回true
@Override
public boolean tryAdvance(Consumer<? super Character> action) {
action.accept(string.charAt(currentChar++));
return currentChar < string.length();
}
@Override
public Spliterator<Character> trySplit() {
int currentSize = string.length() - currentChar;
// 返回null说明拆分的已经足够小,可以顺序处理
if (currentSize < 10) {
return null;
}
// 将试探拆分的位置设定为要解析的String的中间
for (int splitPos = currentSize / 2 + currentChar; splitPos < string.length(); splitPos++) {
// 拆分直到下一个空格
if (Character.isWhitespace(string.charAt(splitPos))) {
// 创建一个新的WordCounterSpliterator来解析String从开始到拆分位置的部分
Spliterator<Character> spliterator =
new WordCounterSpliterator(string.substring(currentChar,
splitPos));
// 将这个WordCounterSpliterator的起始位置设为拆分位置
currentChar = splitPos;
return spliterator;
}
}
return null;
}
@Override
public long estimateSize() {
return string.length() - currentChar;
}
@Override
public int characteristics() {
return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
}
}