原文链接: http://gee.cs.oswego.edu/dl/cpj/jmm.html
Volatile从原子性,可见性和有序性的角度分析,声明为volatile字段的作用相当于一个类通过get/set同步方法保护普通字段,如下:
1 |
final class VFloat { |
2 |
private float value; |
3 |
4 |
final synchronized void set( float f) { value = f; } |
5 |
final synchronized float get() { return value; } |
6 |
} |
与使用synchronized相比,声明一个volatile字段的区别在于没有涉及到锁操作。但特别的是对volatile字段进行“++”这样的读写操作不会被当做原子操作执行。
另外,有序性和可见性仅对volatile字段进行一次读取或更新操作起作用。声明一个引用变量为volatile,不能保证通过该引用变量访问到的非volatile变量的可见性。同理,声明一个数组变量为volatile不能确保数组内元素的可见性。volatile的特性不能在数组内传递,因为数组里的元素不能被声明为volatile。
由于没有涉及到锁操作,声明volatile字段很可能比使用同步的开销更低,至少不会更高。但如果在方法内频繁访问volatile字段,很可能导致更低的性能,这时还不如锁住整个方法。
如果你不需要锁,把字段声明为volatile是不错的选择,但仍需要确保多线程对该字段的正确访问。可以使用volatile的情况包括:
- 该字段不遵循其他字段的不变式。
- 对字段的写操作不依赖于当前值。
- 没有线程违反预期的语义写入非法值。
- 读取操作不依赖于其它非volatile字段的值。
当只有一个线程可以修改字段的值,其它线程可以随时读取,那么把字段声明为volatile是合理的。例如,一个名叫Thermometer(中文:体温计)的类,可以声明temperature字段为volatile。正如在3.4.2节所讨论,一个volatile字段很适合作为完成某些工作的标志。另一个例子在4.4节有描述,通过使用轻量级的执行框架使某些同步工作自动化,但是仍需把结果字段声明为volatile,使其对各个任务都是可见的。
原文
Synchronization and the Java Memory Model
Volatile
In terms of atomicity, visibility, and ordering, declaring a field as volatile is nearly identical in effect to using a little fully synchronized class protecting only that field via get/set methods, as in:
1 |
final class VFloat { |
2 |
private float value; |
3 |
4 |
final synchronized void set( float f) { value = f; } |
5 |
final synchronized float get() { return value; } |
6 |
} |
Declaring a field as volatile differs only in that no locking is involved. In particular, composite read/write operations such as the “++” operation on volatile variables are not performed atomically.
Also, ordering and visibility effects surround only the single access or update to the volatile field itself. Declaring a reference field as volatile does not ensure visibility of non-volatile fields that are accessed via this reference. Similarly, declaring an array field as volatile does not ensure visibility of its elements. Volatility cannot be manually propagated for arrays because array elements themselves cannot be declared as volatile.
Because no locking is involved, declaring fields as volatile is likely to be cheaper than using synchronization, or at least no more expensive. However, if volatile fields are accessed frequently inside methods, their use is likely to lead to slower performance than would locking the entire methods.
Declaring fields as volatile can be useful when you do not need locking for any other reason, yet values must be accurately accessible across multiple threads. This may occur when:
- The field need not obey any invariants with respect to others.
- Writes to the field do not depend on its current value.
- No thread ever writes an illegal value with respect to intended semantics.
- The actions of readers do not depend on values of other non-volatile fields.
Using volatile fields can make sense when it is somehow known that only one thread can change a field, but many other threads are allowed to read it at any time. For example, a Thermometer class might declare its temperature field as volatile. As discussed in 3.4.2, a volatile can be useful as a completion flag. Additional examples are illustrated in 4.4, where the use of lightweight executable frameworks automates some aspects of synchronization, but volatile declarations are needed to ensure that result field values are visible across tasks.