Java并发递增值
我一直在读Java中的Java并发递增值,java,multithreading,concurrency,thread-safety,Java,Multithreading,Concurrency,Thread Safety,我一直在读Java中的volatile和synchronized,但一直在困惑中挠头。我希望有人能帮我解决一个问题 private HashMap<String,int> map = new HashMap<String,int>(); 我的目标是让所有线程共享这个map。如果我添加了volatile,它似乎并没有为我解决问题(我输出并看到map每次都被覆盖)。然后我尝试使用ConcurrentHashMap并在其前面添加volatile。。。这似乎也不起作用。根据我对
volatile
和synchronized
,但一直在困惑中挠头。我希望有人能帮我解决一个问题
private HashMap<String,int> map = new HashMap<String,int>();
我的目标是让所有线程共享这个map
。如果我添加了volatile
,它似乎并没有为我解决问题(我输出并看到map
每次都被覆盖)。然后我尝试使用ConcurrentHashMap
并在其前面添加volatile
。。。这似乎也不起作用。根据我对volatile
的阅读,我的理解是当map
被写入时,它应该“锁定”对map
的访问,然后当map
被写入时,锁被释放
所以。。。然后我尝试添加static
private static ConcurrentHashMap<String,int> map = new ConcurrentHashMap<String,int>();
私有静态ConcurrentHashMap=new ConcurrentHashMap();
这似乎很有效。。。但是我一直在读到使用static
不是正确的方法,因为“争用”(我不太理解)
提前感谢
Volatile
在这里没有帮助<代码>易失性对于解决可见性问题非常有用,但您面临另一个问题:原子性
哦,volatile
与锁定完全无关。它在读/写时不会获得锁,也不会释放任何东西。它所做的是:在写入易失性字段之前发生的所有操作,在读取相同的易失性字段之后,每个其他线程都可以看到。不涉及锁(它们的相似之处在于释放/获取锁的记忆效果完全相同)
操作get
和set
不是原子操作,这意味着这两个操作之间可能会发生其他事情
例如,一个线程将获取
该值,然后另一个线程将获取
相同的值,两者都将增加该值,然后第一个线程将设置
新值,然后第二个线程将执行相同的操作。最终的结果不是你所期望的
此问题最常见的解决方案是将访问(即,同步
)序列化到共享变量,或使用比较和设置(CAS)(因此无需进行同步)
1. <代码>已同步
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;
}
}
假设方法updateValueAndStateIfStateDidntChange
是原子的,并且仅当它能够更新值时才会返回true。这样,如果另一个线程在您获取状态之后和更新值之前修改了该值,那么该方法将返回false,循环将重试
假设您可以以不使用synchronized
(并且可以通过使用java.util.concurrent中的类)的方式实现该方法,您将避免争用(这意味着线程等待获得另一个线程持有的锁),并且您可能会看到性能的总体改善
我在我编写的分布式任务执行系统中使用了很多这种东西。所有的任务都必须执行一次,我有很多机器在执行任务。这些任务都在一个MySQL表中指定。怎么做?您必须有一个用于允许实施CAS的列。称之为执行。在开始任务之前,必须执行以下操作:检索下一个任务,“更新任务集执行=1,其中id=:id和executing=0”
和计算更新的行数。如果您更新了0行,那是因为另一个线程/进程/计算机已经执行了该任务(并成功执行了“更新”查询);在这种情况下,您会忘记它并尝试下一个任务,因为您知道这个任务已经在执行中。如果您更新了1行,那么就可以开始了,您可以执行它
我经常使用CAS的另一个地方是在我编写的一个非常动态(就其配置而言)的资源池中(我主要使用它来管理“连接”,即套接字,但它足够通用,可以容纳任何类型的资源)。基本上,它计算它拥有多少资源。当您尝试获取资源时,它会读取计数器、递减计数器、尝试更新计数器(如果其间没有任何其他修改计数器),如果成功,则您可以从池中获取资源并借出它(计数器达到0后,它将不借出资源)。如果我发布了这段代码,我肯定会在这里添加一个链接。答案当然是使用
AtomicInteger
,而不是int
如果您使用,则需要使用其特殊的线程安全方法
下面是该方法的javadoc摘录:
这相当于
除了动作是原子执行的
private ConcurrentMap=new ConcurrentHashMap();
原子整数值=map.get(“值”);
如果(值==null){
putIfAbsent(“value”,新的AtomicInteger());
value=map.get(“value”);
}
value.incrementAndGet();
请注意,您不必将新值放回原处
如果您对代码做了这样的更改,它应该可以正常工作。Volatile确保线程不会缓存此值。Volatile确保从内存中写入/读取此值是原子的,但它不会在多步骤操作中锁定此变量 将映射替换为ConcurrentHashMap并使用原子CAS操作
使用:ConcurrentHashMap.putIfAbsent()感谢您的快速响应。在一个方面仍然有点困惑:与静态地图相比,同步的好处是什么?当我将映射设置为静态时,我没有遇到一个线程重写另一个线程所做更改的问题(或者我可能是j
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;
}
}
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();