最近我正在阅读一个教程,因为我发现了一个声明说的话。
“Java语言规范保证读取或写入变量是一个原子操作(除非变量的类型为long
或double
)。类型为long
或{的操作变量{1}}只有在使用double
关键字声明时才是原子的。“
volatile
或AtomicInteger
提供原始的AtomicLong
,getAndDecrement()
和getAndIncrement()
等方法。
我对上述陈述感到困惑..请您澄清何时使用 getAndSet()
或AtomicInteger
类。
答案 0 :(得分:51)
执行a = 28
(a
为int
)是原子操作。但是执行a++
不是原子操作,因为它需要读取a的值,增量和对结果的写入。因此,如果您使用a++
来实现线程安全计数器,您可以让两个线程同时读取该值(例如26),然后同时增加它并同时写入它,从而导致27个结果,而不是28。
AtomicInteger通过提供您列出的原子操作来解决此问题。在我的示例中,您将使用incrementAndGet()
,例如,这将保证结束值为28而不是27。
答案 1 :(得分:8)
Atomic意味着操作完成后不会发生任何事情。例如。 AtomicInteger上的getAndDecrement()保证变量同时返回和递减。
如果它不是原子操作,则可能存在值递减(例如从3到2),然后由另一个线程修改(例如,将其从2更改为5),然后返回为5
答案 2 :(得分:4)
如果您需要读取变量并根据读取值编写结果,则需要AtomicInteger
。例如,i++
读取i
(例如3
)并写入i+1
(例如4
)。线程可能同时被中断,另外三个线程也会增加i
。现在我们回来了,i
实际上有值6
,但我们的帖子仍会根据事先读取的内容写4
。
AtomicInteger.getAndIncrement
可确保您不会中断,因此始终正常递增。此外,结果始终刷新到内存,而非易失性i
可能不会刷新到内存。在这种情况下,其他线程甚至可能看不到更改。
答案 3 :(得分:0)
我认为这意味着长读和双读操作是原子的,写操作是原子的。但是读/写不是原子的。
volatile long num;
num = num+1
以上不是线程安全的。读写是两个独立的操作。每个都保证是原子的,但整个表达不是。
为了使线程安全,您需要使用AtomicLong并使用getAndIncrement函数。
答案 4 :(得分:0)
根据您正在处理的数字范围的上限/下限,使用int或long。请不要将长原子的非原子行为与AtomicLong混合。无论你上面写的是什么都是正确的,但你可能会混合这两个概念。在您进行“比较和设置”操作的情况下,AtomicXXX更有用。例如,即使在多线程环境中代码在原子上被修改/读取时也是不正确的:
int i =10
..
..
..
if(i == 10) i++;
在多线程环境中,两个线程可以原子方式访问此代码并更新i的值并使其处于一致状态。因此,处理这种情况通常会保护代码“if(i == 10)i ++;”与同步块。但是,AtomicInteger类提供了实现此类功能的API,而不使用速度较慢的同步块。 AtmoicLong API的情况也是如此
答案 5 :(得分:0)
操作的原子性。做int a = 10;
是一个原子操作,但它不会给你带来问题。提供操作的问题通常是变异,例如a++
或a = a + 2;
等等。
Java规范保证“读取”和“写入”是原子操作而不是它们的组合。因此,根据规范,“读取,添加1然后将结果写回”的操作不是原子操作。这样的操作称为复合操作,它们通常需要在我们的代码中使用它们的原子。
原子类型有助于解决此问题。在原子类型上使用incrementAndget()使得'读取,添加1然后将结果写回来并在上下文中读取新结果'单个原子操作以线程安全。
希望这会有所帮助。顺便说一下,你应该阅读这篇关于并发和线程基础知识的文章(http://walivi.wordpress.com/2013/08/24/concurrency-in-java-a-beginners-introduction/)。它很好地解释了这些东西。