Answers:
有以下两个主要用途AtomicInteger
:
作为一个原子计数器(incrementAndGet()
等),可以同时被多个线程使用
作为支持比较和交换指令(compareAndSet()
)来实现非阻塞算法的原语。
这是BrianGöetz的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()
)而不是增量(并在返回之前处理结果)。
read
和write that value + 1
操作之间的计数器,则将检测到该情况,而不是覆盖旧的更新(避免“丢失的更新”问题)。这实际上是一种特殊情况compareAndSet
-如果旧值是2
,则该类实际调用compareAndSet(2, 3)
-因此,如果与此同时另一个线程修改了该值,则递增方法实际上从头开始重新启动。
我能想到的最简单的绝对示例是使原子操作递增。
使用标准整数:
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范围内的唯一生成器,因为它们非常易于使用,我几乎会认为使用纯算法会过早优化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;
该方法映射是这样的:
++i
是i.incrementAndGet()
i++
是i.getAndIncrement()
--i
是i.decrementAndGet()
i--
是i.getAndDecrement()
i = x
是i.set(x)
x = i
是x = i.get()
还有其他便利方法,例如compareAndSet
或addAndGet
的主要用途AtomicInteger
是在多线程上下文中,并且需要在不使用的情况下对整数执行线程安全操作synchronized
。基本类型上的赋值和检索int
已经是原子的,但是AtomicInteger
附带了许多不是原子的操作int
。
最简单的是getAndXXX
or 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可以用作s中的可变版本,Integer
例如Map
s作为值。
AtomicInteger
用作地图键,因为它使用默认equals()
实现,几乎可以肯定这不是在地图中使用语义所期望的。
例如,我有一个生成某些类实例的库。这些实例中的每个实例都必须具有唯一的整数ID,因为这些实例表示要发送到服务器的命令,并且每个命令都必须具有唯一的ID。由于允许多个线程同时发送命令,因此我使用AtomicInteger生成这些ID。另一种方法是使用某种锁和一个规则的整数,但这既慢又不太优雅。
在Java 8中,原子类已经扩展了两个有趣的功能:
两者都使用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。
当我需要给可以从多个线程访问或创建的对象提供ID时,通常会使用AtomicInteger,并且通常将其用作我在对象的构造函数中访问的类的静态属性。
您可以在原子整数或long上使用compareAndSwap(CAS)实现非阻塞锁。该“TL2”软件事务内存阐述这一点:
我们将特殊版本的写锁与每个事务存储位置相关联。以最简单的形式,版本化的写锁是一个单字自旋锁,它使用CAS操作来获取该锁,并使用存储来释放它。由于只需要一个位来表示已采取锁定,因此我们使用其余的锁定字来保存版本号。
它所描述的是首先读取原子整数。将其拆分为一个忽略的锁定位和版本号。尝试将CAS写入为已清除的锁定位,并将当前版本号写入锁定位集和下一个版本号。循环直到您成功并且您是拥有锁的线程。通过清除锁定位设置当前版本号来解锁。本文描述了使用锁中的版本号来协调线程在写入时具有一致的读取集。
本文介绍了处理器具有对比较和交换操作的硬件支持,这使得处理效率非常高。它还声称:
在低到中度竞争中,使用原子变量的基于CAS的无阻塞计数器比基于锁的计数器具有更好的性能
我使用AtomicInteger解决了餐厅哲学家的问题。
在我的解决方案中,使用AtomicInteger实例表示分叉,每个哲学家需要两个实例。每个哲学家都被标识为1到5的整数。当一个哲学家使用一个分叉时,AtomicInteger持有该哲学家的值,即1到5,否则不使用该叉子,因此AtomicInteger的值是-1。 。
然后,AtomicInteger允许在一个原子操作中检查一个fork是否为free,value ==-1,并将其设置为fork的所有者(如果为free)。请参见下面的代码。
AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){
if (Hungry) {
//if fork is free (==-1) then grab it by denoting who took it
if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
//at least one fork was not succesfully grabbed, release both and try again later
fork0.compareAndSet(p, -1);
fork1.compareAndSet(p, -1);
try {
synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork
lock.wait();//try again later, goes back up the loop
}
} catch (InterruptedException e) {}
} else {
//sucessfully grabbed both forks
transition(fork_l_free_and_fork_r_free);
}
}
}
因为compareAndSet方法不会阻塞,所以它应该增加吞吐量,完成更多工作。如您所知,当需要对资源进行受控访问(即需要分叉)时,就会使用Dining Philosophers问题,就像流程需要资源才能继续工作一样。
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该值未更新