concurrenthashmap线程安全

6yoyoihd  于 2021-06-30  发布在  Java
关注(0)|答案(2)|浏览(475)

我试图澄清hashmap和concurrenthashmap关于类型安全和性能的区别。我看到了很多好文章,但还是很难弄清楚。
让我们以使用concurrenthashmap的以下示例为例,我将尝试为尚未存在的键添加一个值并返回它,新的方法是:

private final Map<K,V> map = new ConcurrentHashMap<>();
    return map.putIfAbsent(k, new Object());

假设我们不想使用putifabsent方法,上面的代码应该是这样的:

private final Map<K,V> map = new ConcurrentHashMap<>();
    synchronized (map) {
        V value = map.get(key); //Edit adding the value fetch inside synchronized block 
        if (!nonNull(value)) {
            map.put(key, new Object());
        }
    }
    return map.get(key)

这种方法的问题是整个Map被锁定,而在第一种方法中,putifabsent方法只在密钥散列所在的bucket上同步,从而导致性能降低吗?第二种方法只需要一个hashmap就可以了吗?

eeq64g8w

eeq64g8w1#

这种方法的问题是整个Map都被锁定了吗
这种方法有两个问题。

这不是内在的

事实上你已经得到了 map 引用没有任何影响,除非涉及(尝试)获取此锁的任何其他代码。关键是, ConcurrentHashmap 它本身不获取此锁。
因此,如果在第二个代码段(使用synchronized)期间,其他线程执行以下操作:

map.putIfAbsent(key, new Object());

那么你的 map.get(key) call返回null,但是您的后续操作 map.put 呼叫结束时覆盖。换句话说,您的线程和运行putifabsent的假设线程都决定编写。
想必,如果这在你的书中是好的,那就奇怪了。为什么使用 putIfAbsent 以及 check if map.get returns null 首先呢?
如果另一个线程这样做:

synchronized (map) {
  map.putIfAbsent(key, new Object());
}

那就没问题了;要么get check if null then set code将被设置,putifabsent调用是noop,要么反之亦然,但它们不可能同时“决定写入”。
它引导我们去;

这是毫无意义的

有两种不同的方法来实现与Map的并发:内在的和外在的。两者都是零,它们不相互作用。
如果你有这样一种结构,所有的访问(读写)都是从一个普通的旧的完全不支持多核的 java.util.HashMap 通过某个共享锁(hashmap示例本身,或者任何其他锁,只要与该特定map示例交互的所有线程都使用同一个锁),那么就可以正常工作,因此没有理由或指向使用 ConcurrentHashMap 相反。
concurrenthashmap的要点是在不使用外部锁定的情况下简化并发进程:让Map执行锁定。
您希望这样做的原因之一是concurrenthashmap impl在它能够执行的任务上明显更快;这些工作是明确的:它是concurrenthashmap拥有的方法。

原子性

代码片段的中心问题是它缺乏原子性。check-then-act在并发模型中根本不存在(在您的示例中:check:key'k'与no value或null关联吗?then-act:set key'k'到value'v'的Map)。这是因为如果你检查的东西在两者之间发生了变化怎么办?如果有两个线程同时“检查并执行”,然后同时运行会怎么样;然后两个线程都先检查,然后都先执行操作,然后会出现损坏的情况:两个线程中的一个线程将执行的状态与您检查时的状态不同,这意味着您的检查已损坏。
正确的模式是先行动后检查:先行动,然后检查操作结果。当然,这需要重新定义并将代码片段中显式编写的代码集成到“行为”阶段的定义中。
换句话说, putIfAbsent 不是一个方便的方法!是最基本的操作!这是唯一的方法(除了外在的锁定)来传达这样一个概念:“执行将‘v’与‘k’关联的动作,但前提是还没有关联。我将在下一步检查此操作的结果”。没有办法把它分解成 if (!map.containsKey(key)) map.put(key, v); 因为check-then-act在并发建模中不起作用。

结论

要么去掉concurrenthashmap,要么去掉synchronized。拥有同时使用这两种语言的代码可能会被破坏,即使不是这样,它也很容易出错,令人困惑,我可以向您保证有更好的方法来编写它(更好的是,它更惯用,更易于阅读,在面对未来的更改请求时更灵活,更易于测试,并且不太可能难以测试其中的bug)。
如果您可以按照chm拥有的方法100%地说明您需要执行的所有操作,那么就这样做吧,因为chm是非常优越的。它甚至有用于任意操作的机制:例如,与基本hashmap不同,即使其他线程也在处理chm,您也可以迭代chm,而对于普通hashmap,您需要在整个操作期间保持锁,这意味着任何其他线程都试图对该hashmap执行任何操作,即使只是“要求它的大小”,也需要等待。因此,对于大多数用例来说,chm会带来更好的性能。

raogr8fs

raogr8fs2#

在第一种方法中 putIfAbsent 方法只在bucket上同步
这是不正确的, ConcurrentHashMap 不同步任何东西,它使用不同的机制,以确保线程安全。
第二种方法只需要一个 HashMap ?
是的,除了第二种方法有缺陷。如果使用同步制作 Map 线程安全,然后 Map 应该使用同步。因此,最好打电话给 Collections.synchronizedMap(map) . 性能将比使用 ConcurrentHashMap .

private final Map<Integer, Object> map = Collections.synchronizedMap(new HashMap<>());

假设我们不想使用 putIfAbsent 方法。
为什么?哦,因为如果钥匙已经在Map上了,那就浪费了分配,这就是为什么我们应该使用 computeIfAbsent() 相反

map.computeIfAbsent(key, k -> new Object());

相关问题