我理解AtomicInteger和其他原子变量允许并发访问。在什么情况下通常使用这个类?


当前回答

AtomicInteger的主要用途是在多线程上下文中,并且需要在不使用synchronized的情况下对整数执行线程安全操作。基本类型int上的赋值和检索已经是原子的,但AtomicInteger附带的许多操作在int上不是原子的。

最简单的是getAndXXX或xXXAndGet。例如,getAndIncrement()是i++的原子等价物,它不是原子的,因为它实际上是三个操作的捷径:检索、加法和赋值。compareAndSet对于实现信号量、锁、锁存器等非常有用。

使用AtomicInteger比使用同步执行相同的操作更快,可读性更强。

一个简单的测试:

public synchronized int incrementNotAtomic() {
    return notAtomic++;
}

public void performTestNotAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        incrementNotAtomic();
    }
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}

public void performTestAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        atomic.getAndIncrement();
    }
    System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}

在我使用Java 1.6的PC上,原子测试在3秒内运行,而同步测试在5.5秒内运行。这里的问题是同步操作(notatomic++)非常短。所以同步的成本相对于操作来说是非常重要的。

除了原子性,AtomicInteger还可以作为一个可变版本的Integer,例如在map中作为值使用。

其他回答

可以在原子整数或长值上使用compareAndSwap (CAS)实现非阻塞锁。“Tl2”软件事务内存论文这样描述:

我们将一个特殊版本的写锁与每个事务关联起来 内存位置。在其最简单的形式中,版本化写锁是 使用CAS操作获取锁和的单字自旋锁 一个发布它的商店。因为我们只需要一个比特来表示 如果锁已被占用,则使用锁字的其余部分来保存 版本号。

What it is describing is first read the atomic integer. Split this up into an ignored lock-bit and the version number. Attempt to CAS write it as the lock-bit cleared with the current version number to the lock-bit set and the next version number. Loop until you succeed and your are the thread which owns the lock. Unlock by setting the current version number with the lock-bit cleared. The paper describes using the version numbers in the locks to coordinate that threads have a consistent set of reads when they write.

本文介绍了处理器对比较和交换操作的硬件支持,这使得比较和交换操作非常高效。它还声称:

使用原子变量的非阻塞基于cas的计数器有更好的性能 在低到中等争用情况下,性能优于基于锁的计数器

AtomicInteger有两个主要用途:

As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms. Here is an example of non-blocking random number generator from Brian Göetz's Java Concurrency In Practice: public class AtomicPseudoRandom extends PseudoRandom { private AtomicInteger seed; AtomicPseudoRandom(int seed) { this.seed = new AtomicInteger(seed); } public int nextInt(int n) { while (true) { int s = seed.get(); int nextSeed = calculateNext(s); if (seed.compareAndSet(s, nextSeed)) { int remainder = s % n; return remainder > 0 ? remainder : remainder + n; } } } ... } As you can see, it basically works almost the same way as incrementAndGet(), but performs arbitrary calculation (calculateNext()) instead of increment (and processes the result before return).

关键是它们允许安全的并发访问和修改。它们通常在多线程环境中用作计数器——在引入它们之前,这必须是一个用户编写的类,将各种方法包装在同步块中。

compareAndSet()函数的简单示例:

import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

        // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(0, 6); 

        // Checks if the value was updated. 
        if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                           + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
      } 
  } 

打印出来的是: 前值:0 该值被更新为6 另一个简单的例子:

    import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val 
            = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

         // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(10, 6); 

          // Checks if the value was updated. 
          if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                               + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
    } 
} 

打印出来的是: 前值:0 没有更新该值

当我需要为可以从多个线程访问或创建的对象提供id时,我通常使用AtomicInteger,并且我通常将它用作我在对象的构造函数中访问的类的静态属性。