Java并发增量值

时间:2011-12-15 05:30:16

标签: java multithreading concurrency thread-safety

我一直在阅读Java中的volatilesynchronized,但我一直在困惑中摸不着头脑。我希望有人可以帮我解决问题

private HashMap<String,int> map = new HashMap<String,int>();

在我的帖子中

if (map.get("value") == null)
{
map.put("value",0);
}
map.put("value",map.get("value")+1);

我的目标是让所有线程共享此map。如果我添加volatile,它似乎不能解决我的问题(我输出并看到map每次都被覆盖)。然后我尝试使用ConcurrentHashMap并在前面添加volatile ......这似乎也没有用。根据我对volatile的解读,我的理解是,当map被写入时,应该“锁定”对map的访问权限,然后在写入map时锁被释放。

所以...然后我尝试添加static

private static ConcurrentHashMap<String,int> map = new ConcurrentHashMap<String,int>();

这看起来效果很好......但是......我一直在阅读使用static并不是正确的方法,因为有些'争用'(我不太明白)

提前致谢

3 个答案:

答案 0 :(得分:10)

Volatile在这里无济于事。 Volatile对于解决可见性问题非常有用,但您面临另一个问题:原子性

哦,volatile锁定完全无关。它在读/写时不会获得锁定,它不会释放任何东西。它的作用是:在读取相同的volatile字段之后, 发生在 之前对所有其他线程的所有操作都将对其他线程可见。没有锁定(它们的相似之处在于释放/获取锁的记忆效应完全相同)。

操作getset不是原子的,这意味着两者之间可能发生其他事情。

例如,一个线程将get该值,然后另一个线程将get相同的值,两者都将增加该值,然后第一个将set新值,然后第二个也会这样做。最终的结果不是你所期望的。

此问题的最常见解决方案是序列化访问(即synchronize)到共享变量,或使用比较和设置(CAS)< / strong>(所以你不需要做同步)。

1。 synchronized

private final Map<String, Integer> m = new ConcurrentHashMap<String, Integer>();
synchronized incrementValue(final String valueName) {
  m.put(valueName, m.get(valueName) + 1);
}

请注意,如果您使用此解决方案,那么每次访问地图都必须在同一个锁上同步

2。 CAS

许多CAS算法已经在JVM中以非常复杂的方式实现(即,它们使用本机代码,JIT可能使用特定于处理器的指令,您无法以其他方式访问 - 检查类{例如,在Sun的JVM中{1}}。

这里可能对您有用的一个类是Unsafe。您可以像这样使用它:

AtomicInteger

CAS算法的作用是这样的:

private final Map<String, AtomicInteger> m = new ConcurrentHashMap<String, AtomicInteger>();
incrementValue(final String valueName) {
  m.get(valueName).incrementAndGet();
}

假设方法for (;;) { state = object.getCurrentState(); if (object.updateValueAndStateIfStateDidntChange(state)) { break; } } 是原子的,只有在能够更新值时才会返回true。这样,如果另一个线程在您获得状态之后和更新值之前修改了该值,则该方法将返回false并且循环将再次尝试它。

假设您可以以不使用updateValueAndStateIfStateDidntChange的方式实现该方法(并且您可以通过在java.util.concurrent中使用类),您将避免争用(这意味着线程在等待获得另一个线程持有的锁),你可能会看到性能的普遍改善。

我在我编写的分布式任务执行系统中使用了很多这种东西。任务必须完全执行一次,我有很多机器执行任务。这些任务都在一个MySQL表中指定。怎么做?您必须有一个目的是允许实施CAS的列。称之为synchronized。在开始任务之前,您必须执行以下操作:检索下一个任务executing计算更新行数。如果您更新了0行,那是因为另一个线程/进程/机器已经执行了该任务(并成功执行了该“更新”查询);在这种情况下,你忘了它并尝试下一个任务,因为你知道这个任务已经被执行了。如果你更新了1行,那么最好去,你可以执行它。

我使用CAS的这个想法的另一个地方是我写的一个非常动态的(关于它的配置)资源池(我主要使用它来管理“连接”,即套接字,但它足够通用持有任何种类的资源)。基本上,它计算它拥有多少资源。当您尝试获取资源时,它会读取计数器,递减计数器,尝试更新它(如果没有其他任何修改计数器),如果这是成功的,那么您可以简单地从池中获取资源并借给它(一旦计数器达到0,它就不会出借资源)。如果我发布了这段代码,我肯定会在这里添加一个链接。

答案 1 :(得分:0)

答案当然是使用AtomicInteger,而不是int

如果您使用ConcurrentHashMap,则需要使用其特殊的线程安全方法putIfAbsent()

这是该方法的javadoc摘录:

  

这相当于

if (!map.containsKey(key))
    return map.put(key, value);
else
    return map.get(key);
  

除了动作以原子方式执行。

private ConcurrentMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>();

AtomicInteger value = map.get("value");
if (value == null) {
    map.putIfAbsent("value", new AtomicInteger());
    value = map.get("value");
}
value.incrementAndGet();

请注意您不必重新设置新值。

如果您对代码进行了更改,那么它应该都可以正常工作。

答案 2 :(得分:0)

Volatile确保线程不缓存此值。易失性确保从内存中写入/读取此值是原子的,但它不能通过多步操作锁定此变量。

用ConcurrentHashMap替换Map并使用原子CAS操作。

使用:ConcurrentHashMap.putIfAbsent()