AtomicInteger的实际用途

我有点理解,AtomicInteger和其他primefacesvariables允许并发访问。 这个class通常在什么情况下使用?

AtomicInteger有两个主要用途:

  • 作为一个primefaces计数器( incrementAndGet()等),可以被许multithreading并发使用

  • 作为支持比较和交换指令( compareAndSet() )来实现非阻塞algorithm的原语。

    下面是BrianGöetz的Java并发实践中的一个非阻塞随机数生成器的例子:

     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() )而不是increment(并且在返回之前处理结果)。

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

使用标准整数:

 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()作为乐观lockingtypes,可以使用更复杂的无同步逻辑 – 获取当前值,基于此计算结果,设置此结果iff值仍然是用于执行计算的input,否则重新开始- 但计数的例子是非常有用的,我会经常使用AtomicIntegers计数和VM范围内的唯一生成器,如果有任何multithreading的暗示参与,因为他们很容易工作,我几乎认为它过早优化使用普通的ints

虽然几乎总是可以通过ints和适当的synchronized声明实现相同的同步保证,但AtomicInteger在于,线程安全性是内置于实际对象本身中的,而不需要担心可能的交织和监视器,每一个碰巧访问int值的方法。 调用getAndIncrement()时意外地违反线程安全性要比返回i++和记住(或不)要事先获取正确的监视器集合要困难得多。

如果你看一下AtomicInteger的方法,你会发现它们往往对应于int的常见操作。 例如:

 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

AtomicInteger的主要用途是当你在一个multithreading的上下文中,并且你需要在一个整数上执行线程安全操作而不使用synchronized 。 原始typesint的赋值和检索已经是primefaces的,但AtomicInteger带有很多在int上不是primefaces的操作。

最简单的是getAndXXX或者xXXAndGet 。 例如getAndIncrement()是一个等价于i++primefaces,它不是primefaces的,因为它实际上是三个操作的缩写:检索,添加和赋值。 compareAndSet对实现信号量,locking,锁存器等非常有用。

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

一个简单的testing:

 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上,primefacestesting在3秒内运行,而同步的运行在5.5秒内。 这里的问题是同步操作( notAtomic++ )非常短。 所以与操作相比,同步的成本是非常重要的。

除了primefaces性AtomicInteger可以用作Integer的可变版本,例如在Map作为值。

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

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

我通常使用AtomicInteger,当我需要给Ids的对象,可以join或从多个线程创build的,我通常使用它作为我在对象的构造函数中访问的类的静态属性。

在Java 8中,primefaces类已经扩展了两个有趣的function:

  • int getAndUpdate(IntUnaryOperator updateFunction)
  • int updateAndGet(IntUnaryOperator updateFunction)

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

  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; } } 

该代码取自Javaprimefaces实例 。

您可以使用compareAndSwap(CAS)对primefaces整数或长整数实现非阻塞锁。 “Tl2”软件事务性内存文件描述了这一点:

我们将特殊的版本化写锁与每个事务处理内存位置相关联。 在最简单的forms中,版本化的写锁是一个单词自旋锁,它使用一个CAS操作来获取锁和一个存储来释放它。 由于只需要一个位来表示锁被占用,我们使用锁字的其余部分来保存一个版本号。

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

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

使用primefacesvariables的非阻塞的基于CAS的计数器比在基于锁的计数器在低到中等的争用中具有更好的性能

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