我理解AtomicInteger和其他原子变量允许并发访问。在什么情况下通常使用这个类?
当前回答
在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原子示例。
其他回答
可以在原子整数或长值上使用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来解决就餐哲学家的问题。
在我的解决方案中,使用AtomicInteger实例来表示fork,每个哲学家需要两个。每个哲学家都被标识为一个整数,从1到5。当一个哲学家使用一个fork时,AtomicInteger保存哲学家的值,从1到5,否则该fork没有被使用,因此AtomicInteger的值为-1。
AtomicInteger允许在一个原子操作中检查一个fork是否空闲,value==-1,如果空闲则将其设置为fork的所有者。参见下面的代码。
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问题是在需要对资源进行受控访问时使用的,即需要fork,就像一个进程需要资源来继续工作一样。
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).
当我需要为可以从多个线程访问或创建的对象提供id时,我通常使用AtomicInteger,并且我通常将它用作我在对象的构造函数中访问的类的静态属性。
关键是它们允许安全的并发访问和修改。它们通常在多线程环境中用作计数器——在引入它们之前,这必须是一个用户编写的类,将各种方法包装在同步块中。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap