2011-01-27 81 views

回答

148

有的AtomicInteger两种主要用途:

  • 为可以由许多线程使用同时

  • 作为支持compare-and-swap指令原语的原子计数器(incrementAndGet()等)(compareAndSet() )来实现非阻塞算法。

    下面是从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; 
          } 
         } 
        } 
        ... 
    } 
    

    正如你可以看到,它基本上作品几乎方式与incrementAndGet()相同,但执行的,而不是增量任意计算(calculateNext()) (并在返回之前处理结果)。

+1

我想我明白了第一次使用。这是为了确保在再次访问属性之前计数器已经增加。正确?你能举一个简短的例子来介绍第二次使用吗? – 2011-01-27 16:11:54

+4

您对第一次使用的理解是真实的 - 它只是确保如果另一个线程修改了“读取”和“写入该值+ 1”操作之间的计数器,则会检测到该值,而不是覆盖旧更新(避免“丢失更新“问题)。这实际上是`compareAndSet`的一个特例 - 如果旧值为`2`,则该类实际调用`compareAndSet(2,3)` - 因此如果另一个线程同时修改了该值,则增量方法将有效地重新启动从一开始就。 – 2011-01-27 16:22:20

+3

“余数> 0→余数:余数+ n;”在这个表达式中是否有一个当n为0时向n添加余数的理由? – sandeepkunkunuru 2016-02-19 16:20:43

1

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

+0

我明白了。这是在属性或实例充当应用程序内的全局变量的情况下。或者还有其他的情况可以想到吗? – 2011-01-27 16:13:40

80

我能想到的绝对最简单的例子是增加原子操作。

随着标准整数:

private volatile int counter; 

public int getNextUniqueIndex() { 
    return counter++; // Not atomic, multiple threads could get the same result 
} 

随着的AtomicInteger:

private AtomicInteger counter; 

public int getNextUniqueIndex() { 
    return counter.getAndIncrement(); 
} 

后者是执行简单的突变的影响(特别是计数,或独特的索引)非常简单的方式,而不必诉诸同步所有访问。

更复杂的自由同步逻辑可以通过使用compareAndSet()作为一种类型的乐观锁的被采用 - 得到的电流值,在此基础上计算结果,设置此结果IFF值仍然是用来做计算的输入,否则重新开始 - 但计数示例非常有用,如果涉及多个线程的任何提示,我经常使用AtomicIntegers进行计数和VM范围的唯一生成器,因为它们很容易与I'd几乎认为使用普通的ints是不成熟的优化。

虽然几乎总是可以实现与ints和适当​​声明相同的同步保障,中AtomicInteger的美妙之处在于线程安全性内置到实际的对象本身,而不是你需要担心的可能的交错,并监视每个发生访问int值的方法。在调用getAndIncrement()时,意外地违反线程安全性要比返回i++并记住(或不)要事先获取正确的监视器集合更困难。

+2

感谢您的明确解释。使用AtomicInteger比方法全部同步的类有什么优势?后者会被认为是“更重”吗? – 2011-01-27 16:14:58

13

例如,我有一个库可以生成某些类的实例。这些实例中的每一个都必须具有唯一的整数ID,因为这些实例表示要发送到服务器的命令,并且每个命令都必须具有唯一的ID。由于允许多个线程同时发送命令,因此我使用AtomicInteger来生成这些ID。另一种方法是使用某种类型的锁和一个常规整数,但这种方法速度较慢,较不优雅。

29

AtomicInteger的主要用途是当您处于多线程环境中,并且您需要在不使用​​的情况下对整数执行线程安全操作。原始类型int上的分配和检索已经是原子的,但AtomicInteger附带了很多在int上不是原子的操作。

最简单的是getAndXXXxXXAndGet。例如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 s作为值。

49

如果你看看AtomicInteger的方法,你会注意到它们倾向于对应于整数上的常见操作。例如:

static AtomicInteger i; 

// Later, in a thread 
int current = i.incrementAndGet(); 

是这种线程安全的版本:

static int i; 

// Later, in a thread 
int current = ++i; 

的方法映射是这样的:
++ii.incrementAndGet()
i++i.getAndIncrement()
--ii.decrementAndGet()
i--i.getAndDecrement()
i = xi.set(x)
x = ix = i.get()

还有其它方便的方法,以及像compareAndSetaddAndGet

5

像gabuzo说,有时我使用的AtomicIntegers时,我想通过引用传递一个int。它是一个内置的类,具有特定于架构的代码,因此比我可以快速编写的任何MutableInteger更容易,更可能更优化。这就是说,这感觉就像是对班级的滥用。

3

您可以使用compareAndSwap(CAS)对原子整数或长整数实现非阻塞锁。该"Tl2" Software Transactional Memory阐述这一点:

我们一个专用的版本写锁定每交易 存储位置相关联。在最简单的形式中,版本化写入锁是一个 单字螺旋锁,它使用CAS操作获取锁,并通过商店释放它。由于只需要一个位来指示 锁已被占用,我们使用锁字的其余部分来保存 版本号。

它描述的是首先读取的原子整数。将其分解为忽略的锁定位和版本号。试图将CAS写入锁定位,并用当前版本号清除锁定位集和下一个版本号。循环直到你成功,你就是拥有锁的线程。通过设置锁定位清零的当前版本号解锁。本文描述了使用锁中的版本号来协调线程在写入时具有一致的一组读取。

This article描述了处理器对比较和交换操作的硬件支持非常有效。它还声称:

无阻塞使用原子变量具有比低基于锁的柜台更好 性能中度争

4

我通常使用的AtomicInteger当我需要给IDS基于CAS的柜台到可以从多个线程接受或创建的对象,并且我通常将它用作我在对象的构造函数中访问的类的静态属性。

5

在Java 8原子类已扩展了两个有趣的功能:

  • INT getAndUpdate(IntUnaryOperator updateFunction)
  • INT updateAndGet(IntUnaryOperator updateFunction)

两者都使用updateFunction到执行原子值的更新。区别在于第一个返回旧值,第二个返回新值。 updateFunction可以实现比标准更复杂的“比较和设置”操作。例如,它可以检查原子计数器不低于零,通常它需要同步,这里的代码是无锁:

public class Counter { 

     private final AtomicInteger number; 

     public Counter(int number) { 
     this.number = new AtomicInteger(number); 
     } 

     /** @return true if still can decrease */ 
     public boolean dec() { 
     // updateAndGet(fn) executed atomically: 
     return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0; 
     } 
    } 

的代码是从Java Atomic Example拍摄。