【问题标题】:Using Atomic and infinite loop to do synchronisation in JAVA在 JAVA 中使用原子和无限循环进行同步
【发布时间】:2016-12-09 01:31:49
【问题描述】:

考虑下面的代码

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();
}

它实现了我想要确保在doInitialization() 完成之前不会调用doMoreStuff(),并且只有第一个线程应该调用doInitialization()

我的问题是,这与将synchronized 块用于整个init() 方法相比如何?

正如我所见,AtomicReference 也使用无限循环(又名忙等待)来浪费 CPU 周期进行更新(请参阅AtomicReference#getAndUpdate()),所以在这里做同样的同步方法可能不是那么糟糕吗?

如果无限循环如此糟糕(例如浪费 CPU 周期),那么为什么 AtomicReference 不使用 synchronized 来停止或唤醒线程?

【问题讨论】:

  • @Thilo 是的,如我的代码所示。带有中断条件的无限循环。
  • if (!initialized.get() && !updating.getAndSet(true)) ...我不喜欢这样,因为我认为它可能允许两个线程都进入关键部分,这在你想要的方面应该是可能的。
  • @TimBiegeleisen 嗯,怎么样?两者都是原子函数。
  • @user1589188 是的,但不是每个调用都需要一条字节码指令吗?那么if 检查怎么可能是完全原子的呢?
  • @TimBiegeleisen 现在怎么样,if 块现在不会重置第二个 AtomicBoolean。所以没有办法两次重新进入区块。

标签: java multithreading synchronization atomic synchronized


【解决方案1】:

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 可能失败的唯一方法是另一个线程在完全相同的时间做同样的事情。

这被称为“重试循环”,应该只执行很少的次数(大约一次)。

【讨论】:

  • 这不是我们想要同步的确切原因吗?并且 AtomicReference 选择使用“重试循环”直到完成,而不是 synchronized 块。
  • 这也称为“乐观锁定”。同步是“悲观锁定”。除非您有很多争用,否则如果您可以在(很少)需要时轻松且廉价地重试更新操作,那么“以防万一”为每个更新取出同步锁的开销是不值得的。
  • 还要注意,getter 是完全无锁和无循环的。
  • 谢谢。现在我明白了。所以真正的问题是,当面临线程争用时,如何确定手头的任务是“乐观”还是“悲观”?
  • 如果您的doInitialization 花费了任何时间,那么loop 可能会忙于等待很长时间。最好在这里使用等待/通知模式。或者一个闩锁。
【解决方案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();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多