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


theme: channing-cyan

这是我参与更文挑战的第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章节再统一分析。


哥佬倌,莫慌到走!觉好留个赞,探讨上评论。欢迎关注面试专栏面时莫慌 | Java并发编程,面试加薪不用愁。也欢迎关注我,一定做一个长更的好男人。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值