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

时间:2016-07-19 23:54:51

标签: java hashmap synchronized

这就是问题所在:我们需要一个哈希表,其条目是线程安全的。

假设我有一个<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);

我认为这是不正确的,因为两个线程可以一个接一个地读取密钥,然后逐个写入并以错误的值结束。

你觉得怎么样?

1 个答案:

答案 0 :(得分:2)

您提出的方法不是线程安全的,因为初始hashTable.get()操作 - 您通过它获取要同步的对象 - 本身不会相对于其他线程put()进行同步与同一个键关联的值。此外,您的代码没有考虑将新值添加到地图或从地图中删除键的可能性(所谓的&#34;结构修改&#34;)。如果可能发生这种情况,无论密钥如何,那么这些操作必须与所有对地图的其他访问同步。

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