同步和Java内存模型(五)Volatile

原文链接: http://gee.cs.oswego.edu/dl/cpj/jmm.html

作者:Doug lea 译者:杜建雄  校对者:方腾飞

Volatile

从原子性,可见性和有序性的角度分析,声明为volatile字段的作用相当于一个类通过get/set同步方法保护普通字段,如下:

[code lang=”java”]
final class VFloat {
private float value;

final synchronized void set(float f) { value = f; }
final synchronized float get() { return value; }
}
[/code]

与使用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:

[code lang=”java”]
final class VFloat {
private float value;

final synchronized void set(float f) { value = f; }
final synchronized float get() { return value; }
}
[/code]

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.

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 同步和Java内存模型(五)Volatile

  • Trackback 关闭
  • 评论 (7)
    • Snway
    • 2013/01/11 9:28上午

    学习了,多谢分享!

    • 酱油
    • 2013/03/15 7:25下午

    声明一个引用变量为volatile,不能保证通过该引用变量访问到的非volatile变量的可见性。同理,声明一个数组变量为volatile不能确保数组内元素的可见性。volatile的特性不能在数组内传递,因为数组里的元素不能被声明为volatile

    如果引用一个非volatile 的final 引用变量,而这个非volatile 能有一个volatile 的state,通过这个引用变量访问 state 是线程安全的么?
    如 :
    class Obj{
    public volatile int state = 0;
    }
    class Test{
    public static final Obj object = new Obj();
    public static void setState(int state){
    object.state = state;
    }
    public static int getState(){
    return object.state;
    }
    // 请问这个类是一个线程安全的类吗?
    }

  1. 把一个变量声明为Volatile后,表示这个变量的值不会被保存到缓存中,而是所有的读写操作都会被直接写到主内存中。这部分知识还可以看看《Effective Java》关于并发部分的介绍。

      • zhili
      • 2018/05/23 1:15下午

      把一个变量声明为Volatile后,表示这个变量的值不会被保存到缓存中,

      并不是说变量的值不会保存到缓存中, 而是对volatile修饰的变量进行store操作的时候会使其他线程对此变量的缓存失效. 从而保证了一致性.

    • ff
    • 2013/06/16 9:08下午

    adu

    • ico_2
    • 2014/05/04 10:29下午

    有序性和可见性仅对volatile字段进行一次读取或更新操作起作用。声明一个引用变量为volatile,不能保证通过该引用变量访问到的非volatile变量的可见性。同理,声明一个数组变量为volatile不能确保数组内元素的可见性。volatile的特性不能在数组内传递,因为数组里的元素不能被声明为volatile。

    举例:
    class A{
    public volatile test=new B();
    }
    class B{
    public int fb=3;
    }
    多个线程操作A的对象a
    如果一个线程
    a.test=new B(33);
    则a.test的新地址立即对其他线程可见,就可以得到新对象的信息
    如果一个线程
    a.test.fb = 33
    则这个修改对其他线程不可见

    • ico_2
    • 2014/05/04 10:36下午

    是对一个volatile属性的写先行发生于对同一个volatile属性的读。
    对引用对象来说,写就是修改地址,重新赋值。
    读的时候也需要用原来的引用方式来读,不可以保存对象的引用后,再读。
    即要用a.test.fb来读,不可
    B myb=a.test
    存下来myb,然后一直读myb.fb

return top