K2x*_*2xL 4 java concurrency multithreading thread-safety
我一直在阅读volatile
和阅读synchronized
Java,但一直在困惑中摸不着头脑.我希望有人可以帮我解决问题
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
每次都被覆盖).然后我尝试使用ConcurrentHashMap
并volatile
在前面添加...这似乎也没有用.根据我的阅读,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字段之前发生的所有操作都将对每个其他线程可见.没有锁定(它们的相似之处在于释放/获取锁的记忆效应完全相同).
的操作get
和set
不是原子,这意味着其他的东西可以在两者之间发生.
例如,一个线程将get
是值,然后另一个线程将是get
相同的值,两者都将增加值,然后第一个将set
是新值,然后第二个将执行相同的操作.最终的结果不是你所期望的.
此问题最常见的解决方案是将访问(即synchronize
)序列化到共享变量,或使用比较和设置(CAS)(因此您不需要进行同步).
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)
请注意,如果您使用此解决方案,那么对地图的每次访问都必须在同一个锁上同步.
许多CAS算法已经在JVM中以非常高效的方式实现(即,它们使用本机代码,并且JIT可能使用特定于处理器的指令,而您无法以其他方式访问) - 检查Unsafe
Sun的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,它就不会出借资源).如果我发布此代码,我肯定会在此处添加一个链接.