我一直在阅读Java中的volatile
和synchronized
,但我一直在困惑中摸不着头脑。我希望有人可以帮我解决问题
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
并不是正确的方法,因为有些'争用'(我不太明白)
提前致谢
答案 0 :(得分:10)
Volatile
在这里无济于事。 Volatile
对于解决可见性问题非常有用,但您面临另一个问题:原子性。
哦,volatile
与锁定完全无关。它在读/写时不会获得锁定,它不会释放任何东西。它的作用是:在读取相同的volatile字段之后, 发生在 之前对所有其他线程的所有操作都将对其他线程可见。没有锁定(它们的相似之处在于释放/获取锁的记忆效应完全相同)。
操作get
和set
不是原子的,这意味着两者之间可能发生其他事情。
例如,一个线程将get
该值,然后另一个线程将get
相同的值,两者都将增加该值,然后第一个将set
新值,然后第二个也会这样做。最终的结果不是你所期望的。
此问题的最常见解决方案是序列化访问(即synchronize
)到共享变量,或使用比较和设置(CAS)< / strong>(所以你不需要做同步)。
synchronized
private final Map<String, Integer> m = new ConcurrentHashMap<String, Integer>();
synchronized incrementValue(final String valueName) {
m.put(valueName, m.get(valueName) + 1);
}
请注意,如果您使用此解决方案,那么每次访问地图都必须在同一个锁上同步。
许多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()