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

时间:2015-05-06 12:42:13

标签: java concurrency

为什么在此代码中使用循环

public final int getAndSet(int newValue) {
    for (;;) {
        int current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}

3 个答案:

答案 0 :(得分:8)

有一种思想流派认为你应该use locks as frugally as you can。即如果可以避免使用,请不要使用锁,如果必须使用锁,请锁定最短时间。这背后的原因在于,首先取得锁定的成本有时相当可观,同时另一个线程的成本在等待,而另一个线程需要锁定它所需的资源。

可以使用very long time cpu指令,称为比较和设置(或简称​​ CAS ),旨在帮助实现以下目的:

setVolumeControlStream(AudioManager.STREAM_MUSIC);

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

想象一下,您希望使用其中一条指令将if (value == providedValue) { value = newValue; return true; } else { return false; } 添加到数字中,以便在高并行加载下始终正常工作。显然,您可以将其编码为:

1

但如果int old = value; if ( compareAndSet(old, old+1) ) { // It worked! } else { // Some other thread incremented it before I got there. } 失败,我们该怎么办?你猜对了 - 再试一次!

CAS

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

使用这个和类似的习惯用法,可以实现许多函数,甚至一些非常复杂的数据结构,根本不使用锁(通常称为 Lock Free )。例如,hereRing Buffer的无锁实现。

答案 1 :(得分:6)

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

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

答案 2 :(得分:4)

  

为什么在此代码中使用循环

通过查看如果不存在可能发生的情况,很容易理解for循环的原因。

假设该方法如下所示:

int current = get();
compareAndSet(current, newValue);
return current;

现在,如果另一个线程出现并同时调用getAndSet,它可能会改变

之间的值
int current = get();

compareAndSet(current, newValue);

compareAndSet将失败,并且该方法无法正常工作。

话虽如此,这不是实现此方法的唯一正确方法。我假设它是以效率的方式实施的。它不是为此操作获取/释放某些锁,而是委托compareAndSet,这可能是通过一些有效的CAS操作实现的。