考虑以下代码
static AtomicBoolean initialized = new AtomicBoolean(false);
static AtomicBoolean initStarted= new AtomicBoolean(false);
public static void init() {
if (!initialized.get() && !initStarted.getAndSet(true)) {
doInitialization();
initialized.set(true);
}
// start waiting
while (!initialized.get());
// finished waiting
doMoreStuff();
}
它实现了我想要确保在doMoreStuff()
完成之前不会调用doInitialization()
并且只有第一个线程应该调用doInitialization()
。
我的问题是,与将synchronized
块与整个init()
方法一起使用相比,这相比如何?
正如我所看到的,AtomicReference也使用无限循环(又称繁忙等待)来浪费CPU周期来进行更新(参见AtomicReference#getAndUpdate()
),所以在这里做同样的方法可能并不是那么糟糕吗?
如果无限循环如此糟糕(例如浪费CPU周期),为什么AtomicReference不使用synchronized
来停止或唤醒线程?
答案 0 :(得分:3)
AtomicReference#getAndUpdate在外部条件发生变化之前没有使用忙等待阻止。
134 * Atomically sets to the given value and returns the old value.
135 *
136 * @param newValue the new value
137 * @return the previous value
138 */
139 public final V getAndSet(V newValue) {
140 while (true) {
141 V x = get();
142 if (compareAndSet(x, newValue))
143 return x;
144 }
145 }
除了争用之外,预计循环只运行一次。
compareAndSet
失败的唯一方法是,如果另一个线程在同一时间做了同样的事情。
这称为"重试循环"并且应该只执行很少次(大约一次)。
答案 1 :(得分:2)
AtomicBoolean.getAndSet如果你只想允许一个线程访问一个特定的块,就像你一样,但我不建议在if语句中使用它与其他可能改变的变量,即使这样案件可能是安全的。但是,while循环在等待时消耗了100%的CPU,所以我建议您使用CountDownLatch。
AtomicBoolean initialized = new AtomicBoolean(false);
CountDownLatch lock = new CountDownLatch(1);
public void init() throws InterruptedException {
if (!initialized.getAndSet(true)) {
doInitialization();
lock.countDown();
}
// start waiting
lock.await();
// finished waiting
doMoreStuff();
}