Java并发增量值

K2x*_*2xL 4 java concurrency multithreading thread-safety

我一直在阅读volatile和阅读synchronizedJava,但一直在困惑中摸不着头脑.我希望有人可以帮我解决问题

private HashMap<String,int> map = new HashMap<String,int>();
Run Code Online (Sandbox Code Playgroud)

在我的线程中

if (map.get("value") == null)
{
map.put("value",0);
}
map.put("value",map.get("value")+1);
Run Code Online (Sandbox Code Playgroud)

我的目标是让所有线程分享这个map.如果我添加volatile它似乎并没有解决我的问题(我输出并看到map每次都被覆盖).然后我尝试使用ConcurrentHashMapvolatile在前面添加...这似乎也没有用.根据我的阅读,volatile我的理解是它应该"锁定"对写入map时的访问权限map,然后在map写入锁定时完成.

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

private static ConcurrentHashMap<String,int> map = new ConcurrentHashMap<String,int>();
Run Code Online (Sandbox Code Playgroud)

这看起来很完美......但是......我一直在读,static由于"争用"(我不太明白),使用不是正确的方法

提前致谢

Bru*_*eis 10

Volatile在这里不会有帮助.Volatile对解决可见性问题很有用,但是你面临另一个问题:原子性.

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

的操作getset不是原子,这意味着其他的东西可以在两者之间发生.

例如,一个线程将get是值,然后另一个线程将是get相同的值,两者都将增加值,然后第一个将set是新值,然后第二个将执行相同的操作.最终的结果不是你所期望的.

此问题最常见的解决方案是将访问(即synchronize)序列化到共享变量,或使用比较和设置(CAS)(因此您不需要进行同步).

1. synchronized

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

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

2. CAS

许多CAS算法已经在JVM中以非常高效的方式实现(即,它们使用本机代码,并且JIT可能使用特定于处理器的指令,而您无法以其他方式访问) - 检查UnsafeSun的JVM中的类例).

这里可能对你有用的一类是AtomicInteger.你可以像这样使用它:

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

CAS算法的作用是这样的:

for (;;) {
  state = object.getCurrentState();
  if (object.updateValueAndStateIfStateDidntChange(state)) {
    break;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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