【面时莫慌】你好,请谈谈volatile关键字?(五)

这是我参与更文挑战的第29天,活动详情查看: 更文挑战
紧接着上一篇你好,请谈谈volatile关键字?(四)

4.6 happens-before

原则上来说本应该在JMM章节讲,happens-before原则是JMM中核心的概念。但既然都讲到可见性和顺序性,那就先简单提提吧。

happens-before原则表示前一操作必定对后续操作可见。也就是说多线程环境下遵守此规则,能满足多操作对共享变量的可见性。

4.6.1 happens-before && as-if-serial

happens-before关系本质上和as-if-serial语义是同一回事。

  • happens-beforeas-if-serial分别保证多线程和单线程执行结果不被改变。
  • as-if-serial语义给使用者带来的表象是单线程按照程序的先后顺序执行的;happens-before语义带来的表象是正
    确同步的多线程程序是按happens-before指定的顺序来执行的。

这两个规则的目的都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

4.6.2 happeds-before 规则

**《JSR-133:Java Memory Model and Thread Specification》**定义了如下happens-before规则。

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。

程序顺序规则 简单的说,为了提供并行度,随便你怎么优化调整执行顺序,但对于结果总是不变的。算是总纲领吧。

监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

监视器锁规则

    static int y = 1;

     static void testMonitorRule(){
         synchronized(Object.class){
             if(y==1){
                 y=2;
             }
         }
    }
复制代码

线程1先获取锁,修改y值为2,线程2获取锁以后,能直接看到y==2。

volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的
读。

volatile变量规则

    static int i = 1;
    static volatile boolean flag = false;
    static void testVolatileRuleReader(){
       i=2;//1
       flag=true;//2
    }
    static void testVolatileRuleWriter(){
        if(flag){//3
            assert i == 2;//4
        }
    }
复制代码

所以步骤3是读,能看到2步骤的写。

传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

传递性规则,就着上面volatile规则的例子说,步骤1肯定是happens before 步骤4。

start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的
ThreadB.start()操作happens-before于线程B中的任意操作。

start()规则

    static void testStartRule(){
        AtomicInteger x = new AtomicInteger(1);
        final Thread t = new Thread(() -> {
            assert x.get() == 2;
        });
        x.set(2);
        t.start();
    }
复制代码

主线程调用子线程t.start()之前所有对共享变量的操作,对子线程都可见。

join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作
happens-before于线程A从ThreadB.join()操作成功返回。

join()规则

static void testJoinRule() throws InterruptedException {
        AtomicInteger x = new AtomicInteger(1);
        final Thread t = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            x.set(2);
        });
        t.start();
        t.join();
        assert  x.get() == 2;
    }
复制代码

在主线程调用子线程t.join(),子线程所有对共享变量的修改主线程都可见。

5 总结

5.1 volatile用法场景

volatileJava 并发中用的很多,比如像 Atomic 包中的 value、以及 AbstractQueuedLongSynchronizer 中的 state 变量都是被 volatile 修饰来用于保证内存可见性。

在我们开发过程中,也有一些应用,比如。

通过修饰标志位,用一个线程去停止另一个线程

public static volatile   boolean run = true;

    public static void main(String[] args) throws InterruptedException {
        final Thread t = new Thread(() -> {
            int i = 0;
            while (!run) {
                i++;
                Thread.yield();
            }
        });
        t.start();
        TimeUnit.SECONDS.sleep(10);
        run= true;
    }
复制代码

双重检查锁实现单例模式

public class DoubleCheckingLock {
    private static volatile Instance instance;
    public static Instance getInstance(){
        if(instance == null){
            synchronized (DoubleCheckingLock.class){
                if(instance == null){
                    instance = new Instance();
                }
            }
        }
        return instance;
    };
    static class  Instance { }
}
复制代码

具体怎么去实现的,请去看看**《Java并发编程的艺术》**这本好书。

5.2 收尾

这篇文章从实例入手,引出了多线程环境下操作共享变量会有可见性,顺序性等问题。结合硬件环境,分析为有效利用计算机资源,提高并发度,CPU引入了高速缓存带来问题。为解决高速缓存带来的一致性问题,又分析了一波MESI协议,最后顺势分析了volatile最核心的原理内存屏障

文章开头提到的问题,看完这篇文章是已经能够回答的七七八八了。关于synchronized这一部分的问题,流程synchronized章节再统一分析。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享