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-before
和as-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
用法场景
volatile
在 Java
并发中用的很多,比如像 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并发编程,面试加薪不用愁。也欢迎关注我,一定做一个长更的好男人。