为什么AtomicInteger和类似类的getAndSet()中有一个循环?

Lal*_*hra 19 java concurrency

出于何种目的,在此代码中使用循环

public final int getAndSet(int newValue) {
    for (;;) {
        int current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}
Run Code Online (Sandbox Code Playgroud)

Old*_*eon 8

有一种思想流派认为你应该尽可能节俭地使用锁.即使你可以避免使用锁,如果必须使用锁,请锁定最短时间.这背后的原因在于,首先取得锁定的成本有时相当可观,而一个线程的成本在等待,而另一个线程则需要对其所需的资源进行锁定.

已经有很长一段时间了,称为比较和设置(或简称CAS)的cpu指令旨在帮助实现这一点:

if (value == providedValue) {
  value = newValue;
  return true;
} else {
  return false;
}
Run Code Online (Sandbox Code Playgroud)

这些指令可以在机器代码级别执行,并且创建锁定的速度要快得多.

想象一下,您希望1使用其中一条指令添加到一个数字,以便在高并行负载下始终正常工作.显然,您可以将其编码为:

int old = value;
if ( compareAndSet(old, old+1) ) {
  // It worked!
} else {
  // Some other thread incremented it before I got there.
}
Run Code Online (Sandbox Code Playgroud)

但如果CAS失败了我们该怎么办?你猜对了 - 再试一次!

boolean succeeded = false;
do {
  int old = value;
  if ( compareAndSet(old, old+1) ) {
    // It worked!
    succeeded = true;
  } else {
    // Some other thread incremented it before I got there. Just try again.
  }
} while (!succeeded);
Run Code Online (Sandbox Code Playgroud)

在那里你可以看到你观察到的模式.

使用这个和类似的习语,可以实现许多功能,甚至一些非常复杂的数据结构,根本不使用锁(通常称为无).例如,是一个Ring Buffer的无锁实现.


Hoo*_*pje 6

这些AtomicXXX类表示原子数据类型.这意味着当两个或多个线程同时访问时,它们必须返回一致的结果.的compareAndSet是通常在硬件直接执行的操作,因此getAndSet在以下方面实现compareAndSet.

该方法的工作原理如下:首先,返回当前值.现在,有可能另一个线程同时更改了值,因此必须检查它,使用compareAndSet情况并非如此.如果另一个线程更改了该值,则必须重复该过程,否则返回错误的值.因此循环.