谨慎 volatile/synchronized 性能损失
Being cautious of the volatile/synchronized performance penalty
考虑 Java 中的统计数据 class,计算成功和失败。
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
行得通吗?一种半边同步。我知道书上说不要这样做。我只是想了解在这种特定情况下它是否会起作用以及为什么。
此外,我应该注意到只有一个线程进行计数。
EDIT请仔细阅读问题是什么。我不是在问如何以不同的方式实现这一点。我在问是否以及为什么有一些半边一致性强制执行,写入线程不关心但 reader 线程主动强制查看最新值。我的直觉是它可能只适用于一个 synchronize
但我无法解释为什么或为什么不。
检查 Java 5 集合和并发 类,它们提供了高于语言原语的更高级别的抽象。
在您的示例中,AtomicLong 应该可以正常工作。
您可以将 class 重构为:
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
中的 classes 只有原子(然后线程安全)方法。
Java 没有提供实现您的 "half-sided" 并发的方法,我什至怀疑硬件是否提供。
但是,您可能想质疑同步保证对您有多重要。的确,语言并不能保证这一点,但只要你在 64 位平台上 运行,从某种意义上说,long
访问肯定会是 "atomic"您的程序不会在两个 32 位读取周期中执行它们,即使既没有 volatile
也没有 synchronized
。这不像你在同步其他任何东西,所以我怀疑你的记录器获得真正的 "latest" 值对你来说真的很重要,而不是一些写回几百个周期的值。
此外,请注意,如果此代码确实对性能至关重要,那么即使完全非同步访问也不是免费的。一旦你在处理器之间共享缓存行,你就会在有问题的 CPU 之间有缓存同步流量,虽然我没有对它或任何东西进行基准测试,但我怀疑添加 volatile
对等式不会有太大的影响。与其他 CPU 进行通信以更改高速缓存行状态的延迟可能比在一个 CPU 的指令流中避免内存障碍是一个更大的问题。
为了避免任何这些惩罚,您可能想要做一些事情,比如让 class 与其他线程共享统计信息,与 "real" 计数器分开,您只更新它例如,每 10,000 次对真实计数器的更新一次。这样,您还可以 volatile
访问共享 class 而不会对编写器线程造成任何常规惩罚。
不,仅同步方法 logStats 是不够的。在您的情况下,您应该使用原子计数器来防止竞争条件,这很可能在高争用下出现。或者在所有方法中使用locks/synchronization,这对于计数器问题
有点大材小用
考虑 Java 中的统计数据 class,计算成功和失败。
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
行得通吗?一种半边同步。我知道书上说不要这样做。我只是想了解在这种特定情况下它是否会起作用以及为什么。
此外,我应该注意到只有一个线程进行计数。
EDIT请仔细阅读问题是什么。我不是在问如何以不同的方式实现这一点。我在问是否以及为什么有一些半边一致性强制执行,写入线程不关心但 reader 线程主动强制查看最新值。我的直觉是它可能只适用于一个 synchronize
但我无法解释为什么或为什么不。
检查 Java 5 集合和并发 类,它们提供了高于语言原语的更高级别的抽象。 在您的示例中,AtomicLong 应该可以正常工作。
您可以将 class 重构为:
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
中的 classes 只有原子(然后线程安全)方法。
Java 没有提供实现您的 "half-sided" 并发的方法,我什至怀疑硬件是否提供。
但是,您可能想质疑同步保证对您有多重要。的确,语言并不能保证这一点,但只要你在 64 位平台上 运行,从某种意义上说,long
访问肯定会是 "atomic"您的程序不会在两个 32 位读取周期中执行它们,即使既没有 volatile
也没有 synchronized
。这不像你在同步其他任何东西,所以我怀疑你的记录器获得真正的 "latest" 值对你来说真的很重要,而不是一些写回几百个周期的值。
此外,请注意,如果此代码确实对性能至关重要,那么即使完全非同步访问也不是免费的。一旦你在处理器之间共享缓存行,你就会在有问题的 CPU 之间有缓存同步流量,虽然我没有对它或任何东西进行基准测试,但我怀疑添加 volatile
对等式不会有太大的影响。与其他 CPU 进行通信以更改高速缓存行状态的延迟可能比在一个 CPU 的指令流中避免内存障碍是一个更大的问题。
为了避免任何这些惩罚,您可能想要做一些事情,比如让 class 与其他线程共享统计信息,与 "real" 计数器分开,您只更新它例如,每 10,000 次对真实计数器的更新一次。这样,您还可以 volatile
访问共享 class 而不会对编写器线程造成任何常规惩罚。
不,仅同步方法 logStats 是不够的。在您的情况下,您应该使用原子计数器来防止竞争条件,这很可能在高争用下出现。或者在所有方法中使用locks/synchronization,这对于计数器问题
有点大材小用