我试图通过用synchronized
替换一些AtomicBoolean
块来减少代码中的线程争用。
以下是synchronized
的示例:
public void toggleCondition() {
synchronized (this.mutex) {
if (this.toggled) {
return;
}
this.toggled = true;
// do other stuff
}
}
使用AtomicBoolean
的替代方案:
public void toggleCondition() {
if (!this.condition.getAndSet(true)) {
// do other stuff
}
}
利用AtomicBoolean
的CAS属性应该比依赖同步更快,所以我运行了little micro-benchmark。
对于10个并发线程和1000000次迭代,AtomicBoolean
仅比synchronized
块稍快一些。
使用AtomicBoolean在toggleCondition()上花费的平均时间(每个线程):0.0338
使用synchronized:0.0357
在toggleCondition()上花费的平均时间(每个主题)我知道微基准值得他们值得,但差异不应该更高吗?
答案 0 :(得分:6)
我知道微基准值得他们值得,但差异不应该更高吗?
我认为问题出在您的基准测试中。看起来每个线程只会将条件切换一次。基准测试将花费大部分时间来创建和销毁线程。任何给定线程在任何其他线程切换的同时切换条件的可能性将接近于零。
当存在对条件的重大争用时,AtomicBoolean具有优于原始锁定的性能优势。对于无条件的情况,我希望看到的差别不大。
更改基准,以便每个线程将条件切换几百万次。这将保证很多锁争用,我希望你会看到性能差异。
修改强>
如果您打算测试的场景只涉及每个线程(和10个线程)的一个切换,那么您的应用程序不太可能会遇到争用,因此使用AtomicBoolean不太可能产生任何差异。
此时,我应该问你为什么要把注意力集中在这个方面。您是否已分析过您的应用程序并确定确实您有锁定争用问题?或者你只是猜测?您是否接受过关于过早优化的弊端的标准讲座?
答案 1 :(得分:3)
看看实际的实现,我的意思是看代码比某些microbenchmark更好(在Java或任何其他GC运行时都不是没用的),我并不感到惊讶它不是“显着更快”。它基本上是一个隐式的同步部分。
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final boolean getAndSet(boolean newValue) {
for (;;) {
boolean current = get();
if (compareAndSet(current, newValue))
return current;
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
然后来自com.sun.Unsafe.java
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
这没有什么神奇之处,资源争用是一个婊子而且非常复杂。这就是为什么使用final
变量并使用不可变数据在Erlang等真正的并发语言中如此普遍。所有这些耗费CPU时间的复杂性都是通过,或者至少转移到不那么复杂的地方。