0

我一直在研究 Java API java.util.concurrent.atomic,尤其是AtomicInteger类。

方法注释说这些方法是原子的。

getAndIncrement()为例:

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

正如它所记录的那样,它是“以原子方式将当前值增加一”。

究竟是什么让这个方法成为原子的?据我所知,它完全是“非原子的”——在其执行过程中以及在语句执行期间涉及许多周期

        int next = current + 1;

例如,next的值可以由另一个线程设置。

4

4 回答 4

2

原子性在compareAndSet(current, next)方法中处理。代码仅在尚未更改时才进行增量并设置新值,并且以原子方式完成(或伪造原子行为)。如果从那以后它被改变了,它需要再试一次。所以它可能不是原子的,但它的行为就像它一样。

于 2013-10-21T10:30:58.233 回答
1

AtomicInteger 使用 volatile 和 CAS(比较和交换)的组合来实现整数计数器的线程安全。

对 volatile 变量的读写具有与使用同步代码块获取和释放监视器相同的内存语义。因此,JMM 保证了 volatile 字段的可见性。

AtomicInteger 类将其 value 字段存储在 volatile 变量中,因此它是传统 volatile 变量的装饰器,但它提供了独特的非阻塞机制,用于在需要对 CAS 的硬件级别支持(比较和设置)后更新值。在低到中等的线程争用情况下,与同步阻塞增量操作相比,原子更新提供了更高的吞吐量。

这是 AtomicInteger 类的 getAndIncrement() 方法的实现。

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

您可以看到没有获取锁来增加值,而是在无限循环中使用 CAS 来更新新值。

由于 AtomicInteger 不需要锁定,因此它可用于编写线程争用低到中等的可扩展应用程序。

于 2013-10-21T10:31:13.600 回答
0

如果你看看没有循环和 CAS 会发生什么,就会清楚这里的原子是什么意思。

在没有同步和比较和设置(因此不需要循环)的情况下,您的代码将类似于

public final int getAndIncrement() {

        int current = get();
        int next = current + 1;
        set(next);
        return current;

}

在多线程环境中,可能有两个线程将当前值视为 0 并尝试将值更新为 1。两次更新,但值仅增加一。显然,如果没有同步,就不能保证不会发生这种情况。

所以这就是 compareAndSet 的帮助,在递增到 1 之前查看值是否仍然为零。所以 incaseof compareAndSet 我们说只有当当前值为零时才将值设置为 1,否则失败。考虑到上述两个线程更新值的情况,最多只有一个线程成功将值增加到 1,其他线程失败。从而尊重操作的原子性(“全有或全无”)。

于 2013-10-21T10:45:37.757 回答
0

如果您一直跟踪 compareAndSet 函数的实现到汇编代码,您会发现它被翻译成单个汇编指令(对于 i486 的 cmpxchg,在 sun jdk 中的文件 linux_i486.s 中),这是一个执行交换的单个指令值,因此提供所需的原子性而不是锁定。

于 2013-10-21T11:06:41.670 回答