computeIfAbsent的使用
程序员文章站
2022-05-04 20:59:36
...
Map中computeIfAbsent的使用
HashMap.computeIfAbsent
如果需要向Map中push一个键值对,需要判断K key在当前map中是否已经存在,不存在则通过后面的 Function<? super K, ? extends V> mappingFunction 来进行value计算,且将结果当作value同key一起push到Map中。
ConcurrentHashMap.computeIfAbsent
ConcurrentHashMap的读写方法具有原子性(pushall是聚合方法,不具有原子性),所以它的computeIfAbsent方法可以在高并发环境下稳定运行。这样避免了使用synchronized,同时可以在高并发下保证一定的性能。
这是因为ConcurrentHashMap.computeIfAbsent是通过 Java 自带的 Unsafe 实现的 CAS。它在虚拟机层面确保了写入数据的原子性,这相对于加锁的效率,会高得多。
常量的声明
//线程数
private static int THREAD_COUNT = 8;
//map的大小
private static int ITEM_COUNT = 8;
//共处理多少次数据
private static int LOOP_COUNT = 10000000;
常见的写法:
private static Map<String, Long> normaledWriting() throws InterruptedException {
ConcurrentHashMap<String, Long> freqs = new ConcurrentHashMap<>(ITEM_COUNT);
ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_COUNT);
forkJoinPool.execute(() -> IntStream.rangeClosed(1, LOOP_COUNT).parallel().forEach(i -> {
String key = "item" + ThreadLocalRandom.current().nextInt(ITEM_COUNT);
synchronized (freqs) {
if (freqs.containsKey(key)) {
freqs.put(key, freqs.get(key) + 1);
} else {
freqs.put(key, 1L);
}
}
}
));
forkJoinPool.shutdown();
//设置最大等待时间
forkJoinPool.awaitTermination(20, TimeUnit.MINUTES);
return freqs;
}
高效率的写法:
private static Map<String, Long> recommendedWriting() throws InterruptedException {
ConcurrentHashMap<String, LongAdder> freqs = new ConcurrentHashMap<>(ITEM_COUNT);
ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_COUNT);
forkJoinPool.execute(() -> IntStream.rangeClosed(1, LOOP_COUNT).parallel().forEach(i -> {
String key = "item" + ThreadLocalRandom.current().nextInt(ITEM_COUNT);
//如果key是null,则赋给value默认的LongAdder
//key每出现一次 给value+1
freqs.computeIfAbsent(key, k -> new LongAdder()).increment();
}
));
forkJoinPool.shutdown();
forkJoinPool.awaitTermination(20, TimeUnit.MINUTES);
return freqs.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().longValue())
);
}
接下来通过java计时器来检测比较效率
public static void main(String[] args) throws InterruptedException {
//添加java计时器,用于比较使用普通的
StopWatch stopWatch = new StopWatch();
stopWatch.start("normaledWriting");
Map<String, Long> normaluse = normaledWriting();
stopWatch.stop();
Assert.isTrue(normaluse.size() == ITEM_COUNT, "normaluse size error");
Assert.isTrue(normaluse.values().stream()
.mapToLong(l -> l).reduce(0, Long::sum) == LOOP_COUNT
, "normaledWriting count error");
stopWatch.start("recommendedWriting");
Map<String, Long> gooduse = recommendedWriting();
stopWatch.stop();
Assert.isTrue(gooduse.size() == ITEM_COUNT, "gooduse size error");
Assert.isTrue(gooduse.values().stream()
.mapToLong(l -> l)
.reduce(0, Long::sum) == LOOP_COUNT
, "recommendedWriting count error");
log.info(stopWatch.prettyPrint());
}
最终运行效果
相关引入包
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;
import java.util.stream.IntStream;