我们可以对每个条目使用Synchronized而不是ConcurrentHashMap吗? [英] Can we use Synchronized for each entry instead of ConcurrentHashMap?

查看:197
本文介绍了我们可以对每个条目使用Synchronized而不是ConcurrentHashMap吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个问题:我们需要一个哈希表,其条目是线程安全的。假设我有一个散列表< String,Long> ,并且我想增加其中一个条目安全地线程化:是以下OK?:

  HashMap< String,Long> hashTable = new HashMap< String,Long>(); 

然后每当我想增加一个入口时:

 同步(hashTable.get(key))
{
Long value = hashTable.get(key);
值++;
hashTable.put(key,value);
}

我认为它比ConcurrentHashMap好,因为它只锁定一个条目,不像ConcurrentHashMap使用桶并将一组条目锁定在一起。

更重要的是,我不知道如何安全地使用COncurrenHashMap来增加它。例如,我认为下面的代码是不正确的:

  ConcurrentHashMap< String,Long> hashTable = new ConcurrentHashMap< String,Long>(); 


Long value = hashTable.get(key);
值++;
hashTable.put(key,value);

我认为这是不正确的,因为两个线程可以一个接一个地读密钥,一个接一个地出现错误的结果。



您认为家伙们是什么? 您的建议方法是不是线程安全的,因为初始的 hashTable.get()操作 - 通过它获得您想要同步的对象 - 本身并不是相对于其他线程同步的 put()与同一个键关联的值。此外,您的代码并未考虑将新值添加到地图或从地图中删除键(所谓的结构修改)的可能性。如果发生这种情况,无论关键是什么,那么这些操作必须与所有对地图的其他访问进行同步。



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



我建议一个稍微不同的方法:使用 ConcurrentHashMap AtomicLong ,这是可变的,作为您的值类型而不是 Long

  ConcurrentHashMap< String,AtomicLong>地图; 

然后,要更新某个键的值,即使您不确定该键已经存在

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

putIfAbsent()确保值对象不会因相互冲突的放置操作而遭到破坏。使用 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() if我们希望避免同步,因为两个线程可能同时尝试为同一个键添加映射。当需要添加新条目时,这样做的成本会更高,但可能比我的第一个建议的平均成本更低。然而,与任何性能问题一样,测试至关重要。


This is the problem: we want a hash table whose entries are thread-safe.

Suppose I have a hash table of <String, Long>, and I want to increase the value of one of the entries thread safely: is the following OK?:

HashMap<String , Long> hashTable = new HashMap<String, Long>();

Then whenever I want to increase an entry:

Synchronized (hashTable.get("key"))
{
    Long value = hashTable.get("key");
    value++;
    hashTable.put("key", value);
}

I think it is better than ConcurrentHashMap, as it locks just one entry, unlike ConcurrentHashMap which uses buckets, and lock a group of entries together.

More importantly, I don't know how to increment it using COncurrenHashMap safely. For example I think the following code is not correct:

 ConcurrentHashMap<String , Long> hashTable = new ConcurrentHashMap<String, Long>();


 Long value = hashTable.get("key");
 value++;
 hashTable.put("key", value);

I think it is not correct, because two threads can read the key one after another, and write one after another and end up in a wrong value.

What do you think guys?

解决方案

Your proposed approach is not thread-safe because the initial hashTable.get() operation -- by which you obtain the object on which you intend to synchronize -- is not itself synchronized relative to other threads put()ing a value associated with the same key. Moreover, your code does not account for the possibility of new values being added to the map or keys being removed from the map (so-called "structural modifications"). If ever that can happen, regardless of key, then those actions have to be synchronized with respect to all other accesses to the map.

You are right, however, that ConcurrentHashMap does not solve these problems either. It is thread-safe with respect to the individual operations it provides, which include some that Map itself does not define, but series of operations that must be performed as an uninterrupted unit still need to be protected by synchronization.

I suggest a slightly different approach: use a ConcurrentHashMap with AtomicLong, which is mutable, as your value type instead of Long:

ConcurrentHashMap<String, AtomicLong> map;

Then, to update the value for a key, even if you're not confident that the key already has an entry in the map, you do this:

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

The putIfAbsent() ensures that value objects are not clobbered by conflicting put operations. The use of AtomicLong avoids the need for multiple operations to be jointly synchronized, because only one map access is needed -- the value retrieved is shared by all threads accessing it, and can itself be atomically updated without further accessing the map.

If you can be certain that the map already has a mapping for the given key, then you can simply do this:

AtomicLong value = map.get(key);
long updatedValue = value.incrementAndGet();

One way or the other, I think this is about the best you can do for the operations you describe and imply.

Update:

You could even consider combining the two approaches like this:

AtomicLong value = map.get(key);

if (value == null) {
    value = map.putIfAbsent(key, new AtomicLong(0));
}

long updatedValue = value.incrementAndGet();

That supposes that it will be comparatively rare that there is not already a mapping for the given key, and it avoids creating a new AtomicLong in that case. If no mapping is found then the map must be accessed a second time to ensure that there is a mapping and to get the corresponding value, but here we still need putIfAbsent() if we want to avoid synchronization, because it is possible for two threads to both try to add a mapping for the same key, at about the same time. That's more costly when a new entry needs to be added, but it's possible that it would turn out to be less costly on average than my first suggestion. As with any performance question, however, it is essential to test.

这篇关于我们可以对每个条目使用Synchronized而不是ConcurrentHashMap吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆