并发学习第五篇——volatile关键字

先给出一个总体的结论

volatile保证并发要解决的三大问题中的可见性和有序性

另外,保证对单次读/写的原子性(复合运算不保证)

前置知识

  JMM模型,见之前的一篇:https://www.cnblogs.com/yb38156/p/9427181.html

  happens-before 

定义:
Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.
规则:
Each action in a thread happens before every subsequent action in that thread.
• An unlock on a monitor happens before every subsequent lock on that monitor.
• A write to a volatile field happens before every subsequent read of that volatile.
• A call to start() on a thread happens before any actions in the started thread.
• All actions in a thread happen before any other thread successfully returns from a join() on that thread.
• If an action a happens before an action b, and b happens before an action c, then a happens before c.

定义讲的是:

  如果a happen-before b,则a所做的任何操作对b是可见的(这里的before不是时间意义上的先后)

规则讲的是:

  • 同一个线程中,前面的操作 happen-before 后续的操作(即单线程内按代码顺序执行。但是,在不影响单线程

   环境执行结果的前提下,编译器和处理器可以进行指令重排序,这是合法的。换句话说,这一规则无法保证编译重排和指令重排)

  • 监视器上的解锁操作 happen-before 其后续的加锁操作。(Synchronized 规则)
  • 对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则)
  • 线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则)
  • 线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作
  • 如果 a happen-before b,b happen-before c,则a happen-before c(传递性)

运行原理

保证可见性

线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作

操作主存中的变量时,每个线程会拷贝一个该变量的副本

当volatile修饰的变量在一个线程中被修改后,会立即强制刷新到主存,并使得其他线程中读取的该变量的副本失效

其他线程再次读取使用该变量时需要重新从主存中加载一份

保证有序性

体现在上面happens-before规则的第三条:对volatile变量的写操作 happen-before 后续的读操作

JVM通过添加"内存屏障"保证volatile的可见性和有序性

对volatile变量进行写操作时,前后分别加StoreStore和StoreLoad屏障

对volatile变量进行读操作时,在读操作之后加LoadLoad和LoadStore屏障

典型应用

java.util.concurrent.atomic包下的各种AtomicBoolean,AtomicInteger中实际保存值的变量,均定义为volatile的

例如AtomicBoolean:

public class AtomicBoolean implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicBoolean.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public AtomicBoolean(boolean initialValue) {
        value = initialValue ? 1 : 0;
    }
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
    ...
}

在很多其他的同步类的源码中,例如ConcurrentHashMap等,也可以看到他的影子

程序员单独在业务中使用,只在单例模式中看到过,不过自己写单例的情况,也早已经被Spring取代了

原文地址:https://www.cnblogs.com/yb38156/p/14434479.html