我正在尝试学习Java中多线程的术语。如果我在以下文本中使用了错误的定义,请纠正我:
来自不同资源的发现
原子操作: 根据Java文档:
在编程中,原子操作是一次有效完成的操作。原子操作不能在中间停止:它要么完全发生,要么根本不发生。原子操作的任何副作用在操作完成之前都不可见。
这就是为什么读取或写入长整型或双精度变量不是原子操作的原因。因为它涉及两个操作,第一个是32位操作,第二个是32位读/写操作。此外,从上面的段落中,我理解如果我在方法上使用synchronized
,它将使该方法成为原子方法(从理论上讲)。
易失变量:也来自Java文档:
这意味着对易失变量的更改始终对其他线程可见。更重要的是,它还意味着当线程读取易失变量时,它不仅看到易失变量的最新更改,而且看到导致更改的代码的副作用。
现在,根据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 位数字。但这并不能使它变成“原子性”,因为“原子性”这一术语的定义在 Java Doc 中有所解释。
nextSerialNumber++
是对nextSerialNumber
的两个不同操作序列,中间至少有一个其他操作:它必须将变量的原始值提取到一个“工作位置”,然后增加工作副本,最后将结果存储回变量。另一方面,this.someNumber = someNumber
只对this.someNumber
执行 一个 操作:它存储新值。 - Solomon Slow