我制作的代码将像多线程环境中的缓存一样使用(如果已经计算了值,我就不会重新计算它),所以,我想知道下面的代码是线程安全的。
public static <K, V> V getOrCreate(ConcurrentHashMap<K, V> map, K key, Function<K, V> function) {
V v = map.get(key);
if (v == null) {
return map.putIfAbsent(key, function.apply(key));
} else {
return v;
}
}
我知道ComputeFabSent,但在阅读了这个问题(为什么concurrenthashmap::putifabsent比concurrenthashmap::ComputeFabSent快?)和在本地环境中的基准测试之后,我找到了另一种方法。这是我基准测试的核心逻辑。获取或创建速度加快约9倍。
BenchmarkModeCntScoreUnits计算机AbsentavGT23790.730ns/opgetorcreateavgt2436.348ns/op
@Benchmark
public void getOrCreate() {
List<String> collect = Arrays.stream(array)
.map(i -> getOrCreate(concurrent2, i, function))
.collect(Collectors.toList());
}
@Benchmark
public void computeIfAbsent() {
List<String> collect = Arrays.stream(array)
.map(i -> concurrent1.computeIfAbsent(i, function))
.collect(Collectors.toList());
}
private static final Integer[] array = { 1, 2, 3, 4, 5, 6, 7, 8 };
static private final Function<Integer, String> function = i -> i.toString() + "!@!@!@";
private static final ConcurrentHashMap<Integer, String> concurrent1 = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Integer, String> concurrent2 = new ConcurrentHashMap<>();
public static <K, V> V getOrCreate(ConcurrentMap<K, V> map, K key, Function<K, V> function) {
V v = map.get(key);
if (v == null) {
map.put(key, function.apply(key));
return map.get(key);
} else {
return v;
}
}
2条答案
按热度按时间1dkrff031#
您的代码是线程安全的,但是有可能对同一个值多次调用该函数。
例如,此竞赛条件:
在这种情况下,thread1和thread2都将执行
function.apply(key)
,但是putIfAbsent
将确保只使用第一个值。如果你使用put
而不是putIfAbsent
在这个竞争条件下,将使用最后一个值。如果你使用
computeIfAbsent
在这个竞争条件下,只使用第一个值,但是function.apply(key)
甚至不会被第二次处决。顺便说一句,你的基准不是很有代表性。
您总是使用不同的密钥,因此不测试使用相同密钥的情况。
据我所知,基准测试是单线程的,因此将跳过所有锁。
baubqpgj2#
这取决于你的数据。
一般来说,putifabsent速度更快,但是如果您尝试访问的大多数密钥都存在,那么computeifabsent可能会更快,因为它避免创建对象,而putifabsent总是创建对象。
您应该真正地对两种情况进行基准测试:一种情况是实际键始终存在,另一种情况是实际键始终不存在。