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