AtomicInteger和volatile

Ach*_*how 27 java multithreading atomic volatile thread-safety

我知道volatile允许可见性,AtomicInteger允许原子性.所以,如果我使用volatile AtomicInteger,是否意味着我不必再使用任何同步机制?

例如.

class A {

    private volatile AtomicInteger count;

    void someMethod(){
        // do something
        if(count.get() < 10) {
            count.incrementAndGet();
        }
}
Run Code Online (Sandbox Code Playgroud)

这线程安全吗?

Jon*_*eet 54

我相信Atomic*实际上既有原子性又有波动性.因此,当您致电(比方说)时AtomicInteger.get(),您可以保证获得最新价值.这包含在java.util.concurrent.atomic 包文档中:

访问和更新原子的记忆效应通常遵循挥发性规则,如Java™语言规范第17.4节所述.

  • get具有读取volatile变量的记忆效应.
  • set具有写入(赋值)volatile变量的记忆效应.
  • lazySet具有写入(赋值)volatile变量的记忆效应,除了它允许对后续(但不是先前的)内存操作进行重新排序,这些内存操作本身不会对普通的非易失性写入施加重新排序约束.在其他用法上下文中,> - lazySet可以在为了垃圾收集而归零时应用,这是一个永远不会再次访问的引用.
  • weakCompareAndSet以原子方式读取并有条件地写入变量,但不会创建任何先前发生的排序,因此不提供与weakCompareAndSet目标之外的任何变量的先前或后续读取和写入相关的保证.
  • compareAndSet和所有其他读取和更新操作(如getAndIncrement)都具有读取和写入volatile变量的内存效果.

现在,如果你有

volatile AtomicInteger count;
Run Code Online (Sandbox Code Playgroud)

volatile部分意味着每个线程将使用最新的AtomicInteger引用,并且这AtomicInteger意味着您还将看到该对象的最新值.

需要这个并不常见(IME) - 因为通常你不会重新分配count引用不同的对象.相反,你有:

private final AtomicInteger count = new AtomicInteger();
Run Code Online (Sandbox Code Playgroud)

此时,它是一个final变量的事实意味着所有线程都将处理同一个对象 - 而且它是一个Atomic*对象的事实意味着它们将在该对象中看到最新的值.


Myg*_*god 6

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

问题是,get并且incrementAndGet是原子的,但if不是。请记住,可以随时暂停非原子操作。例如:

  1. count = 9 目前。
  2. 线程 A 运行if(count.get() <10)并到达true并停在那里。
  3. 线程 B 运行if(count.get() <10)并得到true太多,因此它运行count.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
}
Run Code Online (Sandbox Code Playgroud)