我们可以为每个条目使用 Synchronized 而不是 ConcurrentHashMap 吗?

Can we use Synchronized for each entry instead of ConcurrentHashMap?

这就是问题所在:我们想要一个散列 table,其条目是线程安全的。

假设我有一个 <String, Long> 的散列 table,我想安全地增加其中一个条目线程的值:以下是否可以?:

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 与同一键关联的值。此外,您的代码不考虑将新值添加到地图或从地图中删除键的可能性(所谓的“结构修改”)。如果这种情况可能发生,无论密钥如何,那么这些操作必须与 所有 其他对地图的访问同步。

你是对的,但是,ConcurrentHashMap 也没有解决这些问题。它提供的单个操作是线程安全的,其中包括一些 Map 本身未定义的操作,但 系列 必须作为不间断单元执行的操作还是需要同步保护。

我建议使用一种略有不同的方法:使用 ConcurrentHashMapAtomicLong 作为您的值类型,而不是 Long:

ConcurrentHashMap<String, AtomicLong> map;

然后,要更新一个键的值,即使您不确定该键已经在映射中有一个条目,您也可以这样做:

AtomicLong value = map.putIfAbsent(key, new AtomicLong(0));
long updatedValue = value.incrementAndGet();

putIfAbsent() 确保值对象不会被冲突的 put 操作破坏。 AtomicLong 的使用避免了多个操作联合同步的需要,因为只需要一次映射访问——检索到的值由访问它的所有线程共享,并且本身可以自动更新而无需进一步访问映射.

如果您可以确定地图已经有给定键的映射,那么您可以简单地这样做:

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(),因为有可能两个线程都尝试在大约同一时间为同一个键添加映射。当需要添加新条目时,这样做的成本会更高,但它可能会比我的第一个建议平均成本更低。然而,与任何性能问题一样,测试是必不可少的。