考虑用Java计算成功和失败的统计类。
public class Stat {
long successes=0, failures=0;
public success() {successes += 1;}
public failed() {failures += 1;}
public logStats() { ... read the values and log them ... }
}
应定期从另一个线程调用logStats()
方法以记录当前的统计计数器。这段代码是错误的,因为记录器线程可能看不到最新的值,因为没有任何同步或易失或原子。
因为我使用的是long
,所以即使volatile
也不行。甚至这个makes the increment more expensive than without。假设计数是以非常高的频率完成的,而日志每分钟只运行一次,是否有办法在输入logStats()
时强制将新值分配给所有线程?是否可以仅 logStats()
synchronized
。一种半边同步。我知道这些书说不要这样做。我只是想了解在这个特定的环境中它是否有用以及为什么。
另外我应该注意到只有一个线程会进行计数。
编辑请仔细阅读问题所在。我不是要问如何以不同的方式实现这一点。我问是否以及为什么存在一些半边一致性强制执行,其中写入线程并不关心,但读者线程主动强制查看最新值。我的预感是它可能只与一个synchronize
一起工作,但我无法解释原因或原因。
答案 0 :(得分:2)
Java没有办法实现你的"半边"并发性,我甚至怀疑硬件是否存在。
但是,您可能想要质疑同步保证对您的重要性。确实,语言并不能保证它,但只要你在64位平台上运行,long
访问肯定会是" atomic"从某种意义上说,即使volatile
和synchronized
都没有,你的程序也不会在两个32位读周期中执行它们。它并不像你和其他任何东西同步一样,所以我怀疑你的记录器能够真正获得最新的"最新的"价值,而不是一些写回几百个循环的价值。
另外,请注意,如果此代码确实对性能至关重要,那么即使完全非同步访问也不是免费的。只要您在处理器之间共享缓存行,您就会在相关CPU之间拥有缓存同步流量,而我还没有对它进行基准测试,我怀疑是在添加volatile
这个等式不会产生太大的影响。与其他CPU通信以改变缓存行状态的延迟可能比避免一个CPU指令流中的内存障碍更为严重。
为了避免任何这些处罚,你可能想做一些事情,比如有一个类与其他线程共享统计数据,与"真实"计数器,您只需更新一次,例如,对真实计数器进行10,000次更新。这样,您也可以volatile
访问该共享类,而不会对编写器线程造成任何常规处罚。
答案 1 :(得分:0)
检查Java 5集合和并发类,它们在语言原语之上提供更高级别的抽象。 在您的示例中,AtomicLong应该可以正常工作。
答案 2 :(得分:0)
您可以将您的课程重构为:
import java.util.concurrent.atomic.AtomicLong;
public class Stats {
private final AtomicLong successes = new AtomicLong();
private final AtomicLong failures = new AtomicLong();
public long success() {
return successes.incrementAndGet();
}
public long failed() {
return failures.incrementAndGet();
}
public void logStats() {
final long s = successes.get();
final long f = failures.get();
// do stuffs with s and f
}
}
然后你不必关心并发性,因为java.util.concurrent.atomic
中的类只有原子(然后是线程安全的)方法。
答案 3 :(得分:0)
不,仅使方法logStats同步是不够的。在您的情况下,您应该使用原子计数器来防止竞争条件,这种情况很可能出现在高争用之下。或者在所有方法中使用锁定/同步,这对于反问题
来说有点过分