原子积分和易失性

我知道允许可见性,允许原子性。那么,如果我使用易失性,这是否意味着我不必再使用任何同步机制?volatileAtomicIntegerAtomicInteger

例如。

class A {

    private volatile AtomicInteger count;

    void someMethod(){
        // do something
        if(count.get() < 10) {
            count.incrementAndGet();
        }
}

此线程安全吗?


答案 1

我相信这实际上给出了原子性和波动性。因此,当您调用(说)时,可以保证获得最新值。这在软件包文档中有说明:Atomic*AtomicInteger.get()java.util.concurrent.atomic

原子的访问和更新的内存效应通常遵循易失性规则,如 Java™ 语言规范的第 17.4 节所述。

  • get 具有读取易失性变量的记忆效应。
  • set 具有写入(分配)可变变量的记忆效果。
  • lazySet具有编写(分配)易失性变量的记忆效果,除了它允许对后续(但不是先前的)内存操作进行重新排序,这些操作本身不会对普通非易失性写入施加重新排序约束。在其他使用上下文中,> - lazySet 在清空时可能适用,以便进行垃圾回收,永远不会再次访问的引用。
  • weakCompareAndSet 以原子方式读取和有条件地写入变量,但不创建任何发生之前排序,因此不保证除 weakCompareAndSet 的目标之外的任何变量的先前或后续读取和写入。
  • compareAndSet 和所有其他读取和更新操作(如 getAndIncrement)都具有读取和写入易失性变量的内存效应。

现在,如果您有

volatile AtomicInteger count;

该部分意味着每个线程将使用最新的引用,并且它意味着您还将看到该对象的最新值。volatileAtomicIntegerAtomicInteger

需要此对象并不常见 (IME), 因为通常情况下,您不会重新分配以引用其他对象。相反,您将拥有:count

private final AtomicInteger count = new AtomicInteger();

在这一点上,它是一个变量的事实意味着所有线程都将处理同一个对象 - 而它是一个对象的事实意味着他们将看到该对象中的最新值。finalAtomic*


答案 2

我会说不,它不是线程安全的,如果你将线程安全定义为在单线程模式和多线程模式下具有相同的结果。在单线程模式下,计数永远不会大于 10,但在多线程模式下,它可以。

问题是,它是原子的,但不是。请记住,非原子操作可以随时暂停。例如:getincrementAndGetif

  1. count = 9现在。
  2. 线程 A 运行并到达那里并停止。if(count.get() <10)true
  3. 线程 B 运行并得到,所以它运行并完成。现在。if(count.get() <10)truecount.incrementAndGet()count = 10
  4. 线程 A 恢复并运行 ,现在在单线程模式下永远不会发生。count.incrementAndGet()count = 11

如果你想让它成为线程安全的,而不使用哪个更慢,试试这个实现:synchronized

class A{

final AtomicInteger count;

void someMethod(){
// do something
  if(count.getAndIncrement() <10){
      // safe now
  } else count.getAndDecrement(); // rollback so this thread did nothing to count
}