我有点理解AtomicInteger和其他Atomic变量允许并发访问。在什么情况下这个类通常会被使用?
答案 0 :(得分:171)
AtomicInteger
有两个主要用途:
作为原子计数器(incrementAndGet()
等),可以被多个线程同时使用
作为支持compare-and-swap指令(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 :(得分:92)
我能想到的绝对最简单的例子是增加原子操作。
标准整数:
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++
并记住(或不记住)预先获取正确的监视器集时更容易违反线程安全。
答案 2 :(得分:55)
如果你看一下AtomicInteger的方法,你会注意到它们倾向于对应于整数的常见操作。例如:
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
答案 3 :(得分:35)
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
中作为值。
答案 4 :(得分:16)
例如,我有一个生成某个类实例的库。这些实例中的每一个都必须具有唯一的整数ID,因为这些实例表示发送到服务器的命令,并且每个命令必须具有唯一的ID。由于允许多个线程同时发送命令,因此我使用AtomicInteger生成这些ID。另一种方法是使用某种锁定和常规整数,但这既慢又不太优雅。
答案 5 :(得分:7)
就像gabuzo说的那样,当我想通过引用传递一个int时,我有时会使用AtomicIntegers。它是一个内置类,具有特定于体系结构的代码,因此它比我可以快速编写代码的任何MutableInteger更容易,也可能更优化。也就是说,这感觉就像滥用课堂一样。
答案 6 :(得分:7)
在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。
答案 7 :(得分:5)
当我需要将Ids提供给可以从多个线程接收或创建的对象时,我通常使用AtomicInteger,我通常将它用作我在对象构造函数中访问的类的静态属性。
答案 8 :(得分:4)
您可以在原子整数或长整数上使用compareAndSwap(CAS)实现非阻塞锁。 "Tl2" Software Transactional Memory论文描述了这一点:
我们将特殊版本的写锁与每个事务相关联 记忆位置。最简单的形式是版本化的写锁定 使用CAS操作获取锁定的单字螺旋锁 一个商店来释放它。因为一个只需要一个位来指示 锁定被采取,我们使用其余的锁定字来持有 版本号。
它所描述的是首先读取原子整数。将其拆分为一个忽略的锁定位和版本号。尝试将CAS写入锁定位,将当前版本号清除为锁定位集和下一个版本号。循环直到你成功并且你是拥有锁的线程。通过设置当前版本号并锁定位清除来解锁。本文描述了使用锁中的版本号来协调线程在写入时具有一致的读取集。
This article描述了处理器对比较和交换操作的硬件支持使得效率非常高。它还声称:
使用原子变量的非阻塞基于CAS的计数器更好 性能低于基于锁定的计数器在低到中的争用中
答案 9 :(得分:2)
关键是它们允许安全地进行并发访问和修改。它们通常在多线程环境中用作计数器 - 在它们引入之前,它必须是一个用户编写的类,它包含了同步块中的各种方法。
答案 10 :(得分:1)
我用AtomicInteger解决了餐饮哲学家的问题。
在我的解决方案中,使用AtomicInteger实例表示分叉,每个哲学家需要两个实例。每个哲学家都被标识为1到5的整数。当一个哲学家使用一个分叉时,AtomicInteger持有该哲学家的值,从1到5,否则不使用该分叉,因此AtomicInteger的值是-1。 。
然后,AtomicInteger允许在一次原子操作中检查一个分叉是否空闲,值==-1,并将其设置为该分叉的所有者(如果有空)。参见下面的代码。
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问题,就像流程需要资源来继续工作一样。
答案 11 :(得分:0)
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 该值未更新