这就是问题所在:我们想要一个其条目是线程安全的哈希表。
假设我有一个哈希表 <String, Long>
,并且我想安全地增加其中一个条目线程的值:以下内容可以吗?:
HashMap<String , Long> hashTable = new HashMap<String, Long>();
然后每当我想增加一个条目时:
Synchronized (hashTable.get("key"))
{
Long value = hashTable.get("key");
value++;
hashTable.put("key", value);
}
我认为它比ConcurrentHashMap更好,因为它只锁定一个条目,不像ConcurrentHashMap使用桶,并将一组条目锁定在一起。
更重要的是,我不知道如何使用 COncurrenHashMap 安全地增加它。例如我认为下面的代码是不正确的:
ConcurrentHashMap<String , Long> hashTable = new ConcurrentHashMap<String, Long>();
Long value = hashTable.get("key");
value++;
hashTable.put("key", value);
我认为这是不正确的,因为两个线程可以相继读取 key ,并相继写入并最终得到错误的值。
大家觉得怎么样?
最佳答案
您提出的方法不是线程安全的,因为初始 hashTable.get()
操作(通过该操作获取要同步的对象)本身并不相对于其他线程同步put()
ing 与同一键关联的值。此外,您的代码没有考虑将新值添加到映射或从映射中删除键的可能性(所谓的“结构修改”)。如果发生这种情况,无论键是什么,那么这些操作都必须与 map 的所有其他访问同步。
但是,你是对的,ConcurrentHashMap
也没有解决这些问题。对于它提供的各个操作来说,它是线程安全的,其中包括 Map
的一些操作。本身没有定义,但是必须作为不间断单元执行的一系列操作仍然需要通过同步来保护。
我建议采用稍微不同的方法:使用ConcurrentHashMap
与 AtomicLong
,它是可变的,作为您的值类型而不是 Long
:
ConcurrentHashMap<String, AtomicLong> map;
然后,要更新某个键的值,即使您不确定该键在映射中是否已存在条目,也可以执行以下操作:
AtomicLong value = map.putIfAbsent(key, new AtomicLong(0));
long updatedValue = value.incrementAndGet();
putIfAbsent()
确保值对象不会被冲突的 put 操作破坏。使用AtomicLong
避免了多个操作联契约(Contract)步的需要,因为只需要一次映射访问——检索到的值由访问它的所有线程共享,并且本身可以原子更新,而无需进一步访问映射。
如果您可以确定映射已经具有给定键的映射,那么您可以简单地执行以下操作:
AtomicLong value = map.get(key);
long updatedValue = value.incrementAndGet();
无论怎样,我认为这就是您为您描述和暗示的操作所能做的最好的事情。
更新:
您甚至可以考虑将这两种方法结合起来,如下所示:
AtomicLong value = map.get(key);
if (value == null) {
value = map.putIfAbsent(key, new AtomicLong(0));
}
long updatedValue = value.incrementAndGet();
假设给定键尚不存在映射的情况相对较少,并且它避免创建新的 AtomicLong
在这种情况下。如果没有找到映射则必须再次访问该映射以确保有映射并获取对应的值,但这里我们仍然需要putIfAbsent()
如果我们想避免同步,因为两个线程可能几乎同时尝试为同一个键添加映射。当需要添加新条目时,成本会更高,但平均而言,它的成本可能会比我的第一个建议要低。然而,与任何性能问题一样,测试是必不可少的。
关于java - 我们可以对每个条目使用 Synchronized 而不是 ConcurrentHashMap 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38470322/