在有效的Java书中,它指出:
语言规范保证读或写一个变量是原子,除非变量的类型的
long
或double
[JLS,17.4.7]。
在Java编程或一般编程中,“原子”是什么意思?
volatile long
或volatile double
使其成为原子读写操作。
在有效的Java书中,它指出:
语言规范保证读或写一个变量是原子,除非变量的类型的
long
或double
[JLS,17.4.7]。
在Java编程或一般编程中,“原子”是什么意思?
volatile long
或volatile double
使其成为原子读写操作。
Answers:
这是一个示例,因为一个示例通常比冗长的解释更清晰。假设foo
是类型为的变量long
。以下操作不是原子操作:
foo = 65465498L;
实际上,变量是使用两个单独的操作写入的:一个操作写入前32位,第二个操作写入后32位。这意味着另一个线程可能读取的值foo
,并看到中间状态。
使操作成为原子操作包括使用同步机制,以确保从任何其他线程将操作视为单个原子操作(即不可拆分的部分)。这意味着一旦使该操作成为原子操作,任何其他线程将foo
在赋值之前或赋值之后看到的值。但是永远不要中间值。
一种简单的方法是使变量volatile:
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; }
}
线程调用m2
可能读取0、1234567890L或某个其他随机值,因为i = 1234567890L
不能保证该语句对于a是原子的long
(JVM可以在两个操作中写入前32位和后32位,并且线程可能会i
在两者之间观察到) 。
在Java中,除了long和double以外,所有类型的读取和写入字段都是原子发生的,并且如果使用volatile修饰符声明了该字段,则即使long和double也会被原子读取和写入。也就是说,我们得到的结果是100%,或者那里发生了什么,变量中也没有中间结果。