AtomicBoolean中getAndSet和compareAndSet的区别

22

线程标题应该是自解释的... 我有点困惑于以下方法的规范,这些方法来自AtomicBoolean类:

  • java.util.concurrent.atomic.AtomicBoolean#compareAndSet
  • java.util.concurrent.atomic.AtomicBoolean#getAndSet

我的假设是,当作为布尔子句在if条件中使用时,两者都会产生相同的行为:

public class Test {
  private AtomicBoolean flag = AtomicBoolean(false);

  public void processSomeAction() {
    if (flag.getAndSet(false)) { // Shouldn't this be similar to flag.compareAndSet(false)
      // process some action
    }
  }
  //...
  private void internalMutatorMethod() {
    // do some staff then update the atomic flag
    flas.set(true);
  }
}

假设我想自动检索当前标志值并更新它,这两种方法不应该产生相同的行为吗?如果我忽略了内部差异,请给出每种方法何时以及如何使用的任何解释。

2
compareAndSet有两个参数。Javadoc指出它与getAndSet完全不同。-您可以编写一个调用compareAndSet的方法,使其与getAndSet相同,但这几乎不值得一提。 - laune
4个回答

26

这份文档十分清晰。

  • getAndSet --> "原子地将此布尔值设为给定值并返回以前的值。"
  • compareAndSet --> "如果当前值 == 预期值,则将该值原子地设置为给定的更新值。"

毫不意外,compareAndSet接受两个参数。

在你具体的情况下:

  • if (flag.getAndSet(false)) 只会在其先前的值为 true 的情况下将 flag 设置为 false
  • 这相当于 if (flag.compareAndSet(true, false))

2
是的,正如您所建议的那样,文档非常清晰,一般情况下我也能够看到其中的差异...但我想查看基于我在主线程中编写的代码片段的差异。在那里是否有任何区别? - tmarwen
那正是我期待确认的!在我的情况下它们应该是等价的 :) - tmarwen
@tmarwen,你错误地假设compareAndSet只接受一个参数。 - killjoy
5
“if (flag.getAndSet(false))将仅在其先前的值为true时才将flag设置为false” - 这是不正确的。getAndSet(<val>)无论先前的值是true还是false,始终会将值设置为<val>。 - Vijayakumar Chava

11
您可以查看代码以更好地理解:
public final boolean getAndSet(boolean newValue) {
    for (;;) {
        boolean current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}

getAndSet 方法会将 AtomicBoolean 的值设置为新值,同时返回旧的值。如果在调用 get() 获取旧值和尝试修改其值之间,AtomicBoolean 的布尔值发生了变化,则 compareAndSet 不会修改其值。因此,在 getAndSet 中会循环调用 compareAndSet 直到布尔值设置为新值。

至于您的代码示例:

flag.getAndSet(false) 返回 AtomicBoolean 的旧值。另一方面,flag.compareAndSet(x,false)(注意有两个参数)返回 AtomicBoolean 是否被修改,换句话说,它返回 AtomicBoolean 的旧值是否为 x。


1
当我检查实现时,我发现以下内容。
public final boolean getAndSet(boolean newValue) {
    for (;;) {
        boolean current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}

同时,在检查 javadoc 时,compareAndSet 只有在比较通过时才设置值,而 getAndSet 则仅设置该值并返回先前的值。

1
这仅适用于JDK早期版本。 JDK 8将其重写为do-while循环,而JDK 9到JDK 14使用VarHandle实现,这是本地的。 - AndrewF

0

这个帖子有点老了,但是没有人提到getAndSet比compareAndSet更有效率。 CAS是一条非常昂贵的指令(在所有CPU架构上都是如此,所以JVM在这里并不重要)。因此它们并不是完全等价的。

所以关于OP,两种方法产生相同的行为,但性能不同,尽可能使用getAndSet。


显然这不是真的,特别是当谈到lock cmpxchg时:https://dev59.com/oG435IYBdhLWcg3wkA9e。此外,相比于它对其他核心的影响,该指令本身并不是非常昂贵的:可能会迫使它们再次从主内存中读取。 - JnRouvignac
你能指出相关的部分吗?它们都是缓慢的指令,因为内存锁定确实会影响速度。但至少在最近的英特尔架构中,(锁定)xchg仍然胜出(https://www.agner.org/optimize/instruction_tables.pdf)。cmpxchg更复杂,需要更多的µops,因此速度较慢。 - MappaM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接