为什么Java AtomicInteger中的getAndIncrement()中存在循环?

时间:2017-03-13 15:48:05

标签: java multithreading

getAndIncrement的源代码是:

public final int getAndIncrement() {
   for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}

我不明白为什么会有循环。如果其他一些线程已经改变了值,那么它怎么可能是原子的呢?

让我们说值为5,然后我调用getAndIncrement(),我们期望它为6,但同时其他一些线程将值更改为6,然后getAndIncrement()将将值设为7,这是不期望的。

我哪里错了?

4 个答案:

答案 0 :(得分:7)

循环将继续运行,直到它设法执行get()+1compareAndSet,而没有任何其他线程首先进入compareAndSet。如果另一个线程 得到了compareAndSet,则该线程的compareAndSet将失败,并且循环将重试。

最终结果是每次调用getAndIncrement()都会导致该值只有一个增量。如果该值最初为5,并且两个线程调用getAndIncrement(),则一个将返回6,另一个将返回7.

换句话说:其中一个似乎完全发生在另一个之后,这就是“原子”的含义。

答案 1 :(得分:0)

As already answered,

  

每次调用getAndIncrement()都会导致值

的一个增量

混淆似乎源于你的评论

  

让我们说它的原始值是5,现在我想把它设为6,但是如果其他一些线程已经使它成为6,为什么要重试它呢?

Okey,所以希望系统以一种方式运行,但你正在使用的方法是不同的。 getAndIncrement旨在确保every invocation causes an increment,您想要的是all invocations combined cause ONE increment。很明显,不应该在这里使用getAndIncrement。

值得注意的是,您所期望的行为在单机系统中很少遇到,但在分布式系统中经常会遇到。如果你没有分发,那么其他人就是在你的方法中找错了。

答案 2 :(得分:0)

理解这一点的关键是要了解compareAndSet()的作用:

/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.
 *
 * @param expect the expected value
 * @param update the new value
 * @return true if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

Unsafe.java

/**
 * Atomically update Java variable to <tt>x</tt> if it is currently
 * holding <tt>expected</tt>.
 * @return <tt>true</tt> if successful
 */
public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected,
                                              int x);

因此,此方法使用JVM内部结构原子

  • 检查该值是否具有预期值
    • 如果没有,不做任何事情并返回false
    • 如果是,请设置为新值并返回true

compareAndSet()返回true时,您已找到的循环退出。

for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next))
        return current;
 }

......相当于:

boolean done = false;
int current;
while(!done) {
    current = get();
    int next = current + 1;
    done = compareAndSet(current, next);
}
return current;

......但略显苛刻和清洁。

答案 3 :(得分:0)

@Lily,正如@yshavit所解释的那样,compareAndSet只有在当前仍然有效并且计数器未被另一个线程更新时才会成功。因此它以原子方式更新计数器,否则将返回false。所以它会继续迭代,直到最终成功。在每次迭代时重新计算当前和下一个。因此它会将计数器更新为1或根本不更新。

这是乐观锁定的一种形式,这意味着它不是在其他线程必须检查它们是否可以继续或必须等待的情况下进行锁定,而是根本不锁定而只是继续尝试机会性地直到它成功。理由是这比同步块更便宜,因为通常不需要开销,迭代和再次尝试比在代码块周围锁定更便宜。

顺便说一下。在Oracle java 8中,实现已经改变,现在它在内部使用sun.misc.Unsafe,这可能会调用一些本机逻辑来实现相同的目标。