Java内存模型:看Java如何解决可见性和有序性问题

什么是java内存模型?

导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性,有序性最直接的办法就是禁用缓存和编译优化,但是这样问虽然解决了,我们程序的性能可就堪忧了.

合理的方案应该是按需禁用缓存以及编译优化,那么怎么做到按需禁用呢?对于并发程序,何时禁用缓存以及编译优化只有程序员知道,那所谓的"按需禁用"其实就是指按照程序员的要求来禁用,所以,为了解决可见性和有序性问题,只需要提供程序员按需禁用缓存和编译优化的方法即可.

java内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为,java内存模型规范了JVM如何提供按需禁用和编译优化的方法.具体来说,这些方法包括volatile.synchronizedfinal三个关键字,以及六项Happens-Before规则,这也是重点.

使用volatile的困惑

volatile关键字并不是java语言的特产,古老的C语言里也有,它最原始的意义就是禁用CPU缓存.

例如,声明一个volatile变量volatile int x = 0,它表达的是:告诉编译器,对这个变量的读写,不能使用CPU缓存,必须从内存中读取或者写入.这个语义看上去相当明确,但是实际使用的时候却会带来困惑.

例如下面的代码,假设线程A执行writer()方法,按照volatile语义,会把变量"v = true" 写入内存;假设线程B执行reader()方法,同样按照volatile语义,线程B会从内存中读取变量v,如果线程B看到"v=true"时,那么线程B看到的变量x是多少?

直觉上,应该是42,但实际要看java版本,1.5之前x可能是42,也可能是0; 1.5以上版本运行x就是42.


分析一下,1.5之前版本出现x=0情况变量x可能被CPU缓存而导致可见性问题.这个问题在1.5版本已经被解决,java内存模型在1.5版本对volatile语义进行了增强.答案就是一项Happens-Before规则.

Happens-Before 规则

Happens-Before 并不是说前面一个操作发生在后续操作的前面,它真正要表达的是:前面一个操作的结果对后续操作是可见的。

比较正式的说法是:Happens-Before 约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守 Happens�Before 规则.

1. 程序的顺序性规则

这条规则是指在一个线程中,按照程序顺序,前面的操作Happens-Before于后续的任意操作.这还是比较容易理解的.比如刚才的代码.按照程序的顺序,第6行代码"x=42"Happens-Before于第7行代码"v=true",这就是规则1的内容,也比较符合单线程里面的思维:程序前面对某个变量的修改一定是对后续操作可见的.

2. volatile 变量规则

这条规则是指对一个volatile变量的写操作,Happens-Before于后续对这个volatile变量的读操作.

这个就有点费解了,对一个volatile变量的写操作相对于后续对这个volatile变量的读操作可见,这怎么看都是禁用缓存的意思啊.貌似和1.5版本之前的语义没有变化?如果关联规则3就有不一样的感觉了!

3.传递性

这条规则如果A Happens-Before B,且B Happens-Before C 那么 A Happens-Before C.

应用到代码中:

从图中看出:

1.x=42 Happens-Before 写变量v = true ,这是规则1.

2.写变量v = true Happens-Before 读变量 v = true,这是规则2.

根据传递性,x = 42 Happens-Before 读变量v = true.意味者如果线程B督导了v = true,那么线程A设置的x = 42 对线程B是可见的,也就是说,线程B能看到x == 42.这就是1.5版本对volatile语义的增强,1.5 版本的并发工具包(java.util.concurrent)就是靠 volatile 语义来搞定可见性的.

4. 管程中锁的规则

这条规则是指对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。

管程是一种通用的同步原语,在Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现。

管程中的锁在 Java 里是隐式实现的,例如下面的代码,在进入同步块之前,会自动加锁,而在代码块执行完会自动释放锁,加锁以及释放锁都是编译器帮我们实现的.

假设 x 的初始值是 10,线程 A 执行完代码块后 x 的值会变成 12(执行完自动释放锁),线程 B 进入代码块时,能够看到线程 A 对 x 的写操作,也就是线程 B 能够看到 x==12。

5. 线程 start() 规则

这条是关于线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。

换句话说就是,如果线程 A 调用线程 B 的 start() 方法(即在线程 A 中启动线程 B),那么该 start() 操作 Happens-Before 于线程 B 中的任意操作。

6. 线程 join() 规则

这条是关于线程等待的.它指的是主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),但子线程B完成后(主线程A中join()方法返回),主线程能够看到子线程的操作,看到指的是对共享变量的操作.

换句话说就是,如果在线程 A 中,调用线程 B 的 join() 并成功返回,那么线程 B 中的任意操作 Happens-Before 于该 join() 操作的返回。

被我们忽视的 final

volatile 为的是禁用缓存以及编译优化,

final 修饰变量时,初衷是告诉编译器:这个变量生而不变,可以可劲儿优化。Java 编译器在 1.5 以前的版本的确优化得很努力,以至于都优化错了。问题类似于上一期提到的利用双重检查方法创建单例,构造函数的错误重排导致线程可能看到 final 变量的值会变化

在 1.5 以后 Java 内存模型对 final 类型变量的重排进行了约束

总结

在 Java 语言里面,Happens-Before 的语义本质上是一种可见性,A Happens-Before B意味着 A 事件对 B 事件来说是可见的,无论 A 事件和 B 事件是否发生在同一个线程里。例如 A 事件发生在线程 1 上,B 事件发生在线程 2 上,Happens-Before 规则保证线程 2上也能看到 A 事件的发生。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容