Java 原子操作和多线程

Java 原子操作和多线程,java,multithreading,variables,synchronization,atomicity,Java,Multithreading,Variables,Synchronization,Atomicity,最近我在读一篇教程,在那篇文章中,我看到了一句话,上面写着 “Java语言规范保证读取或写入变量是一个原子操作(除非变量类型为long或double)。类型为long或double的操作变量只有在用volatile关键字声明时才是原子的。” AtomicInteger或AtomicLong,提供原子方法,如getAndDecrement()、getAndIncrement()和getAndSet() 我对上述说法有点困惑。。请澄清何时使用AtomicInteger或AtomicLong类。原子意

最近我在读一篇教程,在那篇文章中,我看到了一句话,上面写着

“Java语言规范保证读取或写入变量是一个原子操作(除非变量类型为
long
double
)。类型为
long
double
的操作变量只有在用
volatile
关键字声明时才是原子的。”

AtomicInteger
AtomicLong
,提供原子方法,如
getAndDecrement()
getAndIncrement()
getAndSet()


我对上述说法有点困惑。。请澄清何时使用
AtomicInteger
AtomicLong
类。

原子意味着操作完成,其间不可能发生任何事情。例如,AtomicInteger上的getAndDecrement()保证同时返回和递减变量

如果它不是一个原子操作,那么该值有可能被递减(例如从3减到2),然后被另一个线程修改(例如从2改为5),然后返回为5。

执行
a=28
(其中
a
int
)是一个原子操作。但是执行
a++
并不是一个原子操作,因为它需要读取a的值、递增和写入a的结果。因此,如果使用
a++
实现线程安全计数器,则可以让两个线程同时读取值(例如26),然后同时递增和写入值,结果是27,而不是28


AtomicInteger通过提供类似于您列出的原子操作来解决此问题。在我的示例中,您将使用
incrementAndGet()
作为示例,这将保证最终值是28而不是27。

我认为这意味着长的双读操作是原子的,而写操作是原子的。但是读+写不是原子的

volatile long num;
num = num+1
上述操作不是线程安全的。读和写是两个独立的操作。每一个都保证是原子的,但整个表达式不是原子的


为了保证线程安全,您需要使用AtomicLong并使用getAndIncrement函数。

根据所处理的数字范围的上限/下限使用int或long。请不要将long的非原子行为与AtomicLong混合。你上面写的任何东西都是正确的,但你可能把这两个概念混在一起了。AtomicXXX在执行“比较和设置”类操作的情况下更有用。例如,即使可以原子地修改/读取int,以下代码在多线程环境中也是不正确的:

int i =10
..
..
..
if(i == 10) i++;

在多线程环境中,两个线程可以原子地访问该代码,并更新i的值,使其处于一致状态。因此,处理这种情况时,通常使用synchronized块来保护代码“if(i==10)i++;”。然而,AtomicInteger类提供了API来实现这些事情,而不使用速度较慢的同步块。AtmoicLong API的情况也是如此,如果需要根据读取的值读取变量并写入结果,则需要一个原子整数。例如,
i++
读取
i
(例如
3
)并写入
i+1
(例如
4
)。一个线程可能同时中断,另外三个线程也会增加
i
。现在我们回来了,
i
实际上具有值
6
,但是我们的线程仍然根据它事先读取的内容写入
4


AtomicInteger.getAndIncrement
确保不会中断,因此始终正确递增。此外,结果总是刷新到内存中,而非易失性
i
可能不会刷新到内存中。在这种情况下,其他线程甚至可能看不到更改。

当您更改变量时,需要操作的原子性。做
inta=10
是一个原子操作,但它不会给您带来问题。给出问题的操作通常是变异操作,如
a++
a=a+2等等

Java规范保证“读”和“写”是原子操作,而不是它们的组合。因此,按照规范,“读取、添加1,然后将结果写回”的操作不是原子操作。这类操作称为复合操作,它们通常需要在代码中使用的上下文中是原子的

原子类型有助于解决这个问题。在原子类型上使用incrementAndget()将“读取、添加1,然后将结果写回并读取新结果”作为上下文中的单个原子操作,以确保线程安全


希望这有帮助。顺便说一句,您应该阅读这篇()关于并发和线程基础知识的文章。它很好地解释了这些东西。

那么,如果我们对“double”或“long”类型的变量执行操作,为什么我们必须使用“volatile”?如果您使用AtomicXxx,您不需要使用volatile,因为原子对象比volatile提供更多的保证。对易失性整数执行
a++
,不会使操作原子化。它只保证另一个线程在a的新值被递增后会看到它。将新值分配给long或double值时,它还保证写入是一个原子操作(因为写入非易失性long或double变量包括写入4个字节,然后写入其他4个字节)。一般来说,更喜欢原子对象而不是易失性变量。
a=28
如果
a
是长或双精度的,则不能保证它是原子的,除非它也是易失性的。@assylias:yes。我没有重复这一点,因为问题中已经正确陈述了这一点。不过我会编辑以澄清。long和double是