在《Effective Java》一书中,它指出:
语言规范保证读写 变量是原子的,除非变量类型为long或double [JLS, 17.4.7]。
“原子”在Java编程或一般编程上下文中是什么意思?
在《Effective Java》一书中,它指出:
语言规范保证读写 变量是原子的,除非变量类型为long或double [JLS, 17.4.7]。
“原子”在Java编程或一般编程上下文中是什么意思?
当前回答
如果你有几个线程执行下面代码中的方法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不能保证长时间都是原子的(JVM可以在两次操作中写入前32位和后32位,线程可能会在中间观察到i)。
其他回答
“原子操作”是指从所有其他线程的角度来看是瞬时的操作。当保证生效时,您不需要担心部分完成的操作。
这是“对系统的其余部分来说,似乎是瞬间发生的”,并且属于计算过程中的线性化范畴。进一步引用这篇链接文章:
原子性是与并发进程隔离的保证。 此外,原子操作通常具有成功或失败 定义-他们要么成功地改变了系统的状态, 或者没有明显的效果。
So, for instance, in the context of a database system, one can have 'atomic commits', meaning that you can push a changeset of updates to a relational database and those changes will either all be submitted, or none of them at all in the event of failure, in this way data does not become corrupt, and consequential of locks and/or queues, the next operation will be a different write or a read, but only after the fact. In the context of variables and threading this is much the same, applied to memory.
你的引用强调了这并不需要在所有情况下都是预期行为。
在Java中,除了long和double之外的所有类型的读写字段都是原子地发生的,如果字段是用volatile修饰符声明的,那么即使long和double也是原子地读写的。也就是说,我们得到了100%的结果,或者得到了100%的结果,在变量中也不可能有任何中间结果。
这里有一个例子:假设foo是一个long类型的变量,那么下面的操作不是一个原子操作(在Java中):
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不能保证长时间都是原子的(JVM可以在两次操作中写入前32位和后32位,线程可能会在中间观察到i)。