我有大约 1000000 个段落的数据库记录,每个段落大约有 500 个字符。通过阅读所有记录,我需要获取按使用最多到最少使用的顺序排列的字母表列表。
我通过创建高达 1000000 的流来模拟数据库读取,然后并行处理该流
final Map<Character, Long> charCountMap = new ConcurrentHashMap<>();
for (char c = 'a'; c <= 'z'; c++) {
charCountMap.put(c, 0l);
}
System.out.println("Parallel Stream");
long start = System.currentTimeMillis();
Stream.iterate(0, i -> i).limit(1000000).parallel() //mock database stream
.forEach(i-> RandomStringUtils.randomAlphanumeric(500)
.toLowerCase().chars().mapToObj(c -> Character.valueOf((char) c)).filter(c -> c >= 97 && c <= 122)
.forEach(c -> charCountMap.compute(c, (k, v) -> v + 1))); //update ConcurrentHashMap
long end = System.currentTimeMillis();
System.out.println("Parallel Stream time spent :" + (end - start));
System.out.println("Serial Stream"); start = System.currentTimeMillis();
Stream.iterate(0, i -> i).limit(1000000) //mock database stream
.forEach(i-> RandomStringUtils.randomAlphanumeric(500)
.toLowerCase().chars().mapToObj(c -> Character.valueOf((char) c)).filter(c -> c >= 97 && c <= 122)
.forEach(c -> charCountMap.compute(c, (k, v) -> v + 1)));
end = System.currentTimeMillis();
System.out.println("Serial Stream time spent :" + (end - start));
我最初认为即使流的预期开销大于 100,000,并行流也会更快。然而,测试表明,即使对于 1,000,000 条记录,串行流也比并行流快约 5 倍。
我怀疑是因为更新了
ConcurrentHashMap
。但是当我删除它并用空函数更改时,仍然存在显着的性能差距。
我的数据库模拟调用或我使用并行流的方式有问题吗?
使用
RandomStringUtils.randomAlphanumeric(500)
不适合与parallel()
一起使用,因为根据此处的代码它使用静态变量来生成随机字符串。因此,所有线程生成随机字符串的所有调用都将在 Random
的同一底层实例上发生争用:
private static final Random RANDOM = new Random();
编写自己的随机字符串生成器,每个线程使用
Random
的单个实例或使用 java.util.concurrent.ThreadLocalRandom - 这可以避免随机序列的争用。在将其编辑为使用 ThreadLocalRandom 之前,同样的问题会导致该问题性能不佳。
参见 javadoc java.util.Random 说:
Instances of java.util.Random are threadsafe.
However, the concurrent use of the same java.util.Random
instance across threads may encounter contention and consequent
poor performance. Consider instead using
java.util.concurrent.ThreadLocalRandom in multithreaded
designs.