【发布时间】:2016-03-27 00:42:22
【问题描述】:
我正在尝试学习 Java 多线程中使用的术语。因此,如果我在以下文本中使用了错误的定义,请纠正我:
我从不同资源中的发现
原子动作: 根据 Java 文档:
在编程中,原子动作是一种有效地同时发生的动作。原子动作不能在中间停止:它要么完全发生,要么根本不发生。在操作完成之前,原子操作的副作用是不可见的。
这就是为什么读取或写入 long 或 double 变量不是原子的。因为它涉及到两个操作,第一个 32 位和第二个 32 位读/写变量。另外,从上面的段落中,我了解到如果我在一个方法上使用synchronized,它将使该方法成为原子(理论上)。
易失性变量:同样来自 Java Doc:
这意味着对 volatile 变量的更改始终对其他线程可见。更重要的是,这也意味着当一个线程读取一个 volatile 变量时,它不仅会看到 volatile 的最新更改,还会看到导致更改的代码的副作用。
现在,同样根据 Joshua Bloch 的 Effective Java 2nd Edition,考虑书中提到的关于 volatile 声明的以下几点:
考虑以下几点:
// Broken - requires synchronization!
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
方法的状态 由单个原子可访问字段 nextSerialNumber 和所有可能的 该字段的值是合法的。因此,不需要同步来保护 它的不变量。不过,如果没有同步,该方法将无法正常工作。
这是因为nextSerialNumber++ 不是原子的,因为它执行读取、递增、写入。
我的总结
所以如果nextSerialNumber++ 不是原子的,并且需要synchronize。那么为什么以下是原子的并且不需要同步?
private static volatile long someNumber = 0;
public static int setNumber(long someNumber) {
return this.someNumber = someNumber;
}
我不明白为什么在 double 或 long 上使用 volatile 会使其成为原子?
因为volatile 所做的只是确保线程 B 尝试读取线程 A 正在写入的 long 变量,并且线程 A 仅写入了 32 位,然后当线程 B访问资源,它将获得由线程 A 写入的 32 位数字。这并不能使其成为 atomic,因为解释了术语 atomic 的定义在 Java 文档中。
【问题讨论】:
-
你只是在这里和自己争论。您在最后一段的第一句话中所做的陈述只是您所做的错误假设,而不是来自规范性参考的陈述。 JLS 17.4 声明“对 volatile 字段的写入发生在该字段的每次后续读取之前”。
-
有一个更有说服力的 JLS 参考资料……看答案。
-
@StephenC 确实,很好。
-
nextSerialNumber++是对nextSerialNumber的两个不同操作的序列,其中至少还有一个其他操作:它必须从变量中获取原始值到工作位置 i>,然后它递增工作副本,然后将结果存储回变量中。另一方面,this.someNumber = someNumber只对this.someNumber执行 一个 操作:它存储新值。
标签: java multithreading concurrency volatile