java concurrenthashmap多线程环境

iezvtpos  于 2021-06-27  发布在  Java
关注(0)|答案(2)|浏览(453)

我制作的代码将像多线程环境中的缓存一样使用(如果已经计算了值,我就不会重新计算它),所以,我想知道下面的代码是线程安全的。

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;
    }

}
1dkrff03

1dkrff031#

您的代码是线程安全的,但是有可能对同一个值多次调用该函数。
例如,此竞赛条件:

//thread 1
V v = map.get(key);
if (v == null) {

    // thread 2 comes in
    {
      V v = map.get(key);
      if (v == null) {
         return map.putIfAbsent(key, function.apply(key));
      } else {
          return v;
      }
    }

    // thread 1 continues
    return map.putIfAbsent(key, function.apply(key));
} else {
    return v;
}

在这种情况下,thread1和thread2都将执行 function.apply(key) ,但是 putIfAbsent 将确保只使用第一个值。如果你使用 put 而不是 putIfAbsent 在这个竞争条件下,将使用最后一个值。
如果你使用 computeIfAbsent 在这个竞争条件下,只使用第一个值,但是 function.apply(key) 甚至不会被第二次处决。
顺便说一句,你的基准不是很有代表性。
您总是使用不同的密钥,因此不测试使用相同密钥的情况。
据我所知,基准测试是单线程的,因此将跳过所有锁。

baubqpgj

baubqpgj2#

这取决于你的数据。
一般来说,putifabsent速度更快,但是如果您尝试访问的大多数密钥都存在,那么computeifabsent可能会更快,因为它避免创建对象,而putifabsent总是创建对象。
您应该真正地对两种情况进行基准测试:一种情况是实际键始终存在,另一种情况是实际键始终不存在。

相关问题