我正在尝试学习Java中多线程使用的术语.所以如果我在下面的文字中使用了错误的定义,请纠正我:
原子动作: 根据Java doc:
在编程中,原子动作是一次有效发生的动作.原子动作不能在中间停止:它要么完全发生,要么根本不发生.在动作完成之前,原子动作的副作用是不可见的.
这就是为什么读或写长变量或双变量不是原子的.因为它涉及两个操作,第一个32位和第二个32位读/写变量.另外,从上面的段落中我了解到,如果我synchronized
在方法上使用它,它将使该方法成为原子(理论上讲).
易变变量:也来自Java Doc:
这意味着对volatile变量的更改始终对其他线程可见.更重要的是,它还意味着当线程读取volatile变量时,它不仅会看到volatile的最新更改,还会看到导致更改的代码的副作用.
现在,根据Joshua Bloch的Effective Java第2版,请考虑以下关于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; }
我不明白为什么使用volatile double
或者long
使其原子化?
因为volatile
它确实是确保线程B是否尝试读取long
线程A正在写入的变量,并且线程A只写了32位,那么当线程B访问资源时,它将获得32由线程A编写的位数.这不会使其成为原子,因为Java Doc中解释了术语atomic的定义.
我不明白为什么在double或long上使用volatile会使它成为原子?
在不使用volatile
关键字的情况下,您可能会读取a的前32位double
或long
由一个线程写入,而另一个32位由另一个线程写入,称为单词撕裂,并且显然不是原子的.
该volatile
关键字可以确保不会发生.您读取的64位值将是由一个线程写入的值,而不是由多个线程写入的某些Franken值.这意味着由于volatile
关键字,这些类型变为原子.
该volatile
关键字不能进行动作等x++
原子,而不管类型(64位或32位),因为它是一个复合操作(读+增量+写),而不是一个简单的写入.a中涉及的操作x++
可以通过其他线程的操作进行交织.该volatile
关键字不能作这样的混合操作的原子.
所以如果
nextSerialNumber++
不是原子的,需要同步.那么为什么以下是原子的并且不需要同步?private static volatile long someNumber = 0; public static int setNumber(long someNumber) { return this.someNumber = someNumber; }
nextSerialNumber++
需要同步,因为它是复合操作,因此不是原子操作.
this.someNumber = someNumber
原则归功于this.someNumber
存在volatile
,并且赋值操作也是原子的,是一个单独的操作.因此无需同步.没有volatile
,this.someNumber
不能以原子方式编写,因此需要同步.
我不明白为什么在double或long上使用volatile会使它成为原子?
这就是原因.使用volatile
a double
或long
使它们成为原子,因为JLS这样说.
JLS(第17.7节)规定:
"写入和读取volatile的long和double值总是原子的."
注意:JLS是规范性的.就语言语义而言,javadoc是非规范的.Bloch的"Effective Java"甚至不是Oracle文档 - 它只是一个(未经授权的)评论.