所有这些方法都是ConcurrentMap
接口的一部分,因此可在所有该接口的实现上调用。此外,最重要的实现ConcurrentHashMap
使用了一些新的方法来改进,便于在映射上执行并行操作。
就像并行流那样,这些方法使用特定的ForkJoinPool
,由Java8中的ForkJoinPool.commonPool()
提供。该池使用了取决于可用核心数量的预置并行机制。我的电脑有四个核心可用,这会使并行性的结果为3:
System.out.println(ForkJoinPool.getCommonPoolParallelism()); // 3
这个值可以通过设置下列JVM参数来增减:
-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
我们使用相同的映射示例来展示,但是这次我们使用具体的ConcurrentHashMap
实现而不是ConcurrentMap
接口,所以我们可以访问这个类的所有公共方法:
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("foo", "bar");
map.put("han", "solo");
map.put("r2", "d2");
map.put("c3", "p0");
Java8引入了三种类型的并行操作:forEach
、search
和 reduce
。这些操作中每个都以四种形式提供,接受以键、值、元素或键值对为参数的函数。
所有这些方法的第一个参数是通用的parallelismThreshold
。这一阈值表示操作并行执行时的最小集合大小。例如,如果你传入阈值500,而映射的实际大小是499,那么操作就会在单线程上串行执行。在下一个例子中,我们使用阈值1,始终强制并行执行来展示。
forEach
forEach()
方法可以并行迭代映射中的键值对。BiConsumer
以当前迭代元素的键和值调用。为了将并行执行可视化,我们向控制台打印了当前线程的名称。要注意在我这里底层的ForkJoinPool
最多使用三个线程。
map.forEach(1, (key, value) ->
System.out.printf("key: %s; value: %s; thread: %s\n",
key, value, Thread.currentThread().getName()));
// key: r2; value: d2; thread: main
// key: foo; value: bar; thread: ForkJoinPool.commonPool-worker-1
// key: han; value: solo; thread: ForkJoinPool.commonPool-worker-2
// key: c3; value: p0; thread: main
search
search()
方法接受BiFunction
并为当前的键值对返回一个非空的搜索结果,或者在当前迭代不匹配任何搜索条件时返回null
。只要返回了非空的结果,就不会往下搜索了。要记住ConcurrentHashMap
是无序的。搜索函数应该不依赖于映射实际的处理顺序。如果映射的多个元素都满足指定搜索函数,结果是非确定的。
String result = map.search(1, (key, value) -> {
System.out.println(Thread.currentThread().getName());
if ("foo".equals(key)) {
return value;
}
return null;
});
System.out.println("Result: " + result);
// ForkJoinPool.commonPool-worker-2
// main
// ForkJoinPool.commonPool-worker-3
// Result: bar
下面是另一个例子,仅仅搜索映射中的值:
String result = map.searchValues(1, value -> {
System.out.println(Thread.currentThread().getName());
if (value.length() > 3) {
return value;
}
return null;
});
System.out.println("Result: " + result);
// ForkJoinPool.commonPool-worker-2
// main
// main
// ForkJoinPool.commonPool-worker-1
// Result: solo
reduce
reduce()
方法已经在Java 8 的数据流之中用过了,它接受两个BiFunction
类型的lambda表达式。第一个函数将每个键值对转换为任意类型的单一值。第二个函数将所有这些转换后的值组合为单一结果,并忽略所有可能的null
值。
String result = map.reduce(1,
(key, value) -> {
System.out.println("Transform: " + Thread.currentThread().getName());
return key + "=" + value;
},
(s1, s2) -> {
System.out.println("Reduce: " + Thread.currentThread().getName());
return s1 + ", " + s2;
});
System.out.println("Result: " + result);
// Transform: ForkJoinPool.commonPool-worker-2
// Transform: main
// Transform: ForkJoinPool.commonPool-worker-3
// Reduce: ForkJoinPool.commonPool-worker-3
// Transform: main
// Reduce: main
// Reduce: main
// Result: r2=d2, c3=p0, han=solo, foo=bar
我希望你能喜欢我的Java8并发系列教程的第三部分。这个教程的代码示例托管在Github上,还有许多其它的Java8代码片段。欢迎fork我的仓库并自己尝试。
如果你想要支持我的工作,请向你的朋友分享这篇教程。你也可以在Twiiter上关注我,因为我会不断推送一些Java或编程相关的东西。