在《高效Java》一书中,它指出:
语言规范保证阅读或书写变量是原子的,除非该变量是类型CCD_ 1或CCD_,17.4.7].
在Java编程或一般编程的上下文中,"原子"是什么意思?
这里有一个例子:假设foo
是long
类型的变量,那么以下操作不是原子操作(在Java中(:
foo = 65465498L;
事实上,变量是使用两个单独的操作写入的:一个写入前32位,另一个写入后32位。这意味着另一个线程可能读取foo
的值,并查看中间状态。
使操作成为原子操作包括使用同步机制,以确保从任何其他线程将操作视为单个原子操作(即不可拆分为多个部分(。这意味着,一旦操作成为原子操作,任何其他线程都将在赋值之前或之后看到foo
的值。但决不是中间值。
一个简单的方法是使变量不稳定:
private volatile long foo;
或者同步对变量的每次访问:
public synchronized void setFoo(long value) {
this.foo = value;
}
public synchronized long getFoo() {
return this.foo;
}
// no other use of foo outside of these two methods, unless also synchronized
或者将其替换为AtomicLong
:
private AtomicLong foo;
"原子操作"是指从所有其他线程的角度来看似乎是即时的操作。当担保适用时,您不需要担心部分完整的操作。
它"在系统的其他部分看来是瞬间发生的",在计算过程中属于线性化的范畴。进一步引用链接文章:
原子性是与并发进程隔离的保证。此外,原子操作通常有成功或失败定义——它们要么成功地改变了系统的状态,或者没有明显的效果。
因此,例如,在数据库系统的上下文中,可以有"原子提交",这意味着你可以将更新的变更集推送到关系数据库,这些变更要么全部提交,要么在发生故障时根本不提交,这样数据就不会损坏,并且由于锁和/或队列,下一个操作将是不同的写入或读取,但仅在事实之后。在变量和线程的上下文中,这与应用于内存的情况基本相同。
你的话强调了这需要而不是在所有情况下都是预期行为。
刚刚发现后原子与非原子操作对我很有帮助。
"如果一个作用于共享内存的操作相对于其他线程在一个步骤中完成,那么它就是原子操作
当在共享内存上执行原子存储时,没有其他线程可以观察到修改已完成一半。
当对共享变量执行原子加载时,它会读取在某个时刻出现的整个值。">
如果您有几个线程执行下面代码中的方法m1和m2:
class SomeClass {
private int i = 0;
public void m1() { i = 5; }
public int m2() { return i; }
}
您可以保证任何调用m2
的线程都将读取0或5。
另一方面,使用此代码(其中i
是长代码(:
class SomeClass {
private long i = 0;
public void m1() { i = 1234567890L; }
public long m2() { return i; }
}
调用long
0的线程可以读取0、1234567890L或某个其他随机值,因为语句i = 1234567890L
不能保证对于long
是原子的(JVM可以在两个操作中写入前32位和后32位,线程可能会观察到介于两者之间的i
(。
在Java中,除long和double之外的所有类型的读写字段都以原子方式发生,如果该字段是用volatile修饰符声明的,则即使是long和double也以原子方式读写。也就是说,我们100%地得到了那里发生的事情,也不可能在变量中有任何中间结果。
简单地说,原子意味着操作要么完成,要么不完成。其他线程或CPU不会在操作过程中捕捉到它。