Volatile
在这里没有帮助。Volatile
有利于解决能见度问题,但你还面临另一个问题:原子性.
Oh, and volatile
绝对没有关系locking。它在读/写时不会获取锁,也不会释放任何东西。它的作用是这样的:所有的动作发生在之前在读取相同的易失性字段后,对易失性字段的写入将对所有其他线程可见。不涉及锁定(它们的相似之处在于释放/获取锁的内存效应完全相同)。
行动get
and set
不是原子的,这意味着两者之间可能会发生其他事情。
例如,一个线程将get
该值,然后另一个线程将get
相同的值,两者都会增加该值,然后第一个会增加set
新值,那么第二个将执行相同的操作。最后的结果并不是你所期望的。
这个问题最常见的解决方案是串行访问 (ie, 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);
}
请注意,如果您使用此解决方案,则对地图的每次访问都必须同步在同一个锁上.
2. CAS
许多 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();
}
CAS 算法的作用如下:
for (;;) {
state = object.getCurrentState();
if (object.updateValueAndStateIfStateDidntChange(state)) {
break;
}
}
假设该方法updateValueAndStateIfStateDidntChange
是原子的,并且仅当能够更新值时才会返回 true。这样,如果另一个线程在您获取状态之后和更新值之前修改了该值,该方法将返回 false 并且循环将再次尝试。
假设您可以以不使用的方式实现该方法synchronized
(并且您可以通过使用 java.util.concurrent 中的类),您将避免争用(这意味着线程等待获取另一个线程持有的锁),并且您可能会看到性能的总体改进。
我在自己写的分布式任务执行系统中大量使用了这种东西。这些任务必须全部执行一次,并且我有很多机器正在执行任务。这些任务都在单个 MySQL 表中指定。怎么做?您必须有一个列,其目的是允许实施 CAS。叫它executing
。在开始任务之前,您必须执行以下操作:检索下一个任务,"update tasks set executing = 1 where id = :id AND executing = 0"
and 计算更新的行数。如果您更新了 0 行,那是因为另一个线程/进程/机器已经执行了该任务(并成功执行了该“更新”查询);在这种情况下,您会忘记它并尝试下一个任务,因为您知道这个任务已经在执行。如果你更新了1行,那么就可以了,你可以执行它。
我经常使用 CAS 概念的另一个地方是我编写的一个非常动态的(就其配置而言)资源池(我主要用它来管理“连接”,即套接字,但它足够通用,可以容纳任何有点儿resource)。基本上,它计算它拥有多少资源。当您尝试获取资源时,它会读取计数器,递减它,尝试更新它(如果中间没有其他任何内容修改计数器),如果成功,那么您可以简单地从池中获取资源并将其借出(一旦计数器达到 0,就不会借出资源)。如果我发布此代码,我一定会在此处添加指向它的链接。