在Effective Java一书中,它指出:
语言规范保证阅读或写作 变量是原子的,除非变量的类型为
long
或double
[JLS, 17.4.7]。
“原子”在Java编程或编程环境中意味着什么?
答案 0 :(得分:327)
这是一个例子,因为一个例子通常比一个长的解释更清楚。假设foo
是long
类型的变量。以下操作不是原子操作:
foo = 65465498L;
实际上,变量是使用两个独立的操作写入的:一个写入前32位,另一个写入最后32位。这意味着另一个线程可能会读取foo
的值,并看到中间状态。
使操作原子化包括使用同步机制,以确保从任何其他线程看到操作为单个原子(即不可拆分的部分)操作。这意味着任何其他线程,一旦操作成为原子操作,将在赋值之前或赋值之后看到foo
的值。但绝不是中间价值。
这样做的一个简单方法是制作variable 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;
答案 1 :(得分:49)
“原子操作”是指从所有其他线程的角度看似乎是瞬时的操作。保证适用时,您无需担心部分完成的操作。
答案 2 :(得分:21)
这是“系统其他部分即时发生”的东西,并且在计算过程中属于Linearizability的分类。引用该链接文章:
原子性是与并发进程隔离的保证。 此外,原子操作通常具有成功或失败 定义 - 他们要么成功地改变了系统的状态, 或没有明显效果。
因此,例如,在数据库系统的上下文中,可以进行“原子提交”,这意味着您可以将更改的更改集推送到关系数据库,并且这些更改将全部提交,或者都不提交在发生故障的情况下,以这种方式数据不会损坏,并且锁定和/或队列的结果,下一个操作将是不同的写入或读取,但只有之后事实。在变量和线程的上下文中,这大致相同,适用于内存。
您的引言强调,在所有情况下,不都是预期的行为。
答案 3 :(得分:11)
如果您有多个线程在下面的代码中执行方法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
的语句long
不能保证是原子的(JVM可以写第一个)两个操作中的32位和最后32位,并且线程可能会在其间观察到i
。
答案 4 :(得分:11)
刚发现帖子Atomic vs. Non-Atomic Operations对我很有帮助。
"如果相对于其他线程在一个步骤中完成,则对共享内存执行的操作是原子操作。
当在共享内存上执行原子存储时,没有其他线程可以观察到修改半完成。
当对共享变量执行原子加载时,它会读取单个时刻出现的整个值。"
答案 5 :(得分:0)
在Java中,除了long和double以外,所有类型的读取和写入字段都是原子发生的,并且如果使用volatile修饰符声明了该字段,则即使long和double也会被原子读取和写入。也就是说,我们得到的结果是100%,或者那里发生了什么,变量中也没有中间结果。