在《Effective Java》一书中,它指出:
语言规范保证读写a 变量是原子的,除非变量的类型是long或double [JLS, 17.4.7] . < / p >
long
double
“原子”在Java编程或一般编程上下文中是什么意思?
它是“对系统的其余部分来说是瞬间发生的”,并且在计算过程中属于Linearizability的类别。进一步引用这篇链接文章:
原子性是与并发进程隔离的保证。 此外,原子操作通常具有成功或失败 定义-他们要么成功地改变了系统的状态,
因此,例如,在数据库系统的上下文中,可以有“原子提交”,这意味着你可以将更新的变更集推送到关系数据库,这些更改要么全部提交,要么在失败的情况下根本不提交,这样数据就不会被损坏,而锁和/或队列的后果是,下一个操作将是不同的写或读,但只有后这个事实。在变量和线程的上下文中,应用于内存也是一样的。
你的引用强调,这需要不是预期的行为在所有情况下。
下面是一个例子:假设foo是一个类型为long的变量,那么下面的操作不是一个原子操作(在Java中):
foo
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替换它:
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。
m2
另一方面,对于这段代码(其中i是一个长类型):
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)。
i = 1234567890L
“原子操作”是指从所有其他线程的角度来看是瞬时的操作。当保证生效时,您不需要担心部分完成的操作。
刚刚发现一个帖子原子操作与非原子操作对我很有帮助。
作用于共享内存的操作是原子的,如果它相对于其他线程在一个步骤中完成。 当在共享内存上执行原子存储时,没有其他线程可以观察到修改已完成一半。 当对共享变量执行原子加载时,它会读取该变量在某一时刻出现的整个值。”
作用于共享内存的操作是原子的,如果它相对于其他线程在一个步骤中完成。
当在共享内存上执行原子存储时,没有其他线程可以观察到修改已完成一半。
当对共享变量执行原子加载时,它会读取该变量在某一时刻出现的整个值。”
在Java中,除了long和double之外的所有类型的读写字段都是原子地发生的,如果字段是用volatile修饰符声明的,那么即使long和double也是原子地读写的。也就是说,我们得到了100%的结果,或者得到了100%的结果,在变量中也不可能有任何中间结果。