Java多线程(二)volatile关键字

1 volatile的定义

Java语言规范第三版中对volatile定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能够被准确和一致地更新,线程应该取保通过排它锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

2 volatile 的内存语义

一旦一个 共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:

2.1  保证了不同线程对这个变量进行 读取 时的可见性,即一个线程修改了某个变量的值 , 这新值对其他线程来说是立即可见的 。(volatile  解决了线程间 共享变量 的可见性问题)

2.1.1 使用 volatile 关键字会强制 将修改的值立即写入主存;

2.1.2 使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的量 工作内存中缓存变量 stop  的缓存行无效(反映到硬件层的话,就是 CPU 的 L1或者 L2 缓存中对应的缓存行无效)

2.1.3 由于线程 1 的工作内存中缓存变量 stop 的缓存行无效,所以线程 1再次读取变量 stop 的值时 会去主存读取。那么,在线程 2 修改 stop 值时(当然这里包括 2 个操作,修改线程 2 工作内存中的值,然后将修改后的值写入内存),会使得线程 1 的工作内存中缓存变量 stop 的缓存行无效,然后线程 1 读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。那么线程 1 读取到的就是最新的正确的值。

2.2 禁止进行指令重排序 ,阻止编译器对代码的优化 。

volatile 关键字禁止指令重排序有两层意思:

2.2.1 当程序执行到 volatile  变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且 结果已经对后面的操作 可见;在其后面的操作肯定还没有进行。

2.2.2 在进行指令优化时,不能把 volatile 变量前面的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

为了实现 volatile 的内存语义,加入 volatile 关键字时,编译器在生成字节码时,会在指令序列中插入内存屏障,会多出一个 lock 前缀指令。内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题。编译器和 CPU 可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。处理器在进行重排序时是会考虑指令之间的数据依赖性。

内存屏障 有 2 个作用:

1)先于这个 内存屏障 的 指令 必须先执行,后于这个 内存屏障的指令 必须后执行 。

2) 使得内存可见性。所以, 如果你的字段是 volatile ,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据。在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存。

lock 前缀指令在多核处理器下会引发了两件事情:

1).将当前处理器中这个变量所在缓存行的数据会写回到系统内存。

2)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

内存屏障可以被分为以下几种类型:

LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2,在 Load2 及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。

StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2,在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。

LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2,在 Store2 及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕。

StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2,在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

3 volatile写-读的内存语义

3.1 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的值)消息。

3.2 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所修改的值)消息。

3.3 线程A写一个volatile变量,随后线程B读取这个volatile变量,这个过程实质上是线程A通过主存向线程B发送消息。


4   线程之间的通信机制

4.1 通信方式的种类

线程之间的通信一共有两种方式:共享内存 和 消息传递。

共享内存 :指的是多条线程共享同一片内存,发送者将消息写入内存,接收者从内存中读取消息,从而实现了消息的传递。但这种方式有个弊端,即需要程序员来控制线程的同步,即线程的执行次序。这种方式并没有真正地实现消息传递,只是从结果上来看就像是将消息从一条线程传递到了另一条线程。

消息传递: 顾名思义,消息传递指的是发送线程直接将消息传递给接收线程。由于执行次序由并发机制完成,因此不需要程序员添加额外的同步机制,但需要声明消息发送和接收的代码。

Java使用共享内存的方式实现多线程之间的消息传递。因此,程序员需要写额外的代码用于线程之间的同步。

5 Java内存模型

Java 内存模型规定所有的变量都是存在 主存当中,每个线程都有自己的 工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,并且每个线程不能访问其他线程的工作内存。

5.1 Java内存模型通信结构示意图如下


Java 内存模型规定所有的变量都是存在 主存当中,每个线程都有自己的 工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,并且每个线程不能访问其他线程的工作内存。


从图来看,如果线程A与线程B之间要通信的话,必须要经历下面2个步骤。

1) 线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2) 线程B到主内存中去读取线程A之前已经更新过的共享变量。


6 volatile一定能保证原子性吗?

答案是否的,如果修改实例变量中的数据,比如i++;也就是i=i+1;则这样的操作其实并不是一个原子操作。也就是非线程安全的。表达式i++的操作步骤分解如下:

1)从内存中取出i的值;

2)计算i的值;

3)将i的值写到内存。

假如在第2步计算值的时候,另外一个线程也修改i的值,那么这个时候就会出现脏数据。解决的办法使用syschronized关键字。关于syschronized关键字明天继续更新。

7 volatile 和 和 synchronized 区别 。

1) volatile 是变量修饰符,而 synchronized 则作用于代码块或方法。

2) volatile 不会对变量加锁,不会造成线程的阻塞;synchronized 会对变量加锁,可能会造成线程的阻塞。

3) volatile 仅能实现变量的修改可见性,并不能保证原子性;而synchronized 则 可 以 保 证 变 量 的 修 改 可 见 性 和 原 子 性 。(synchronized 有两个重要含义:它确保了一次只有一个线程可以执行代码的受保护部分(互斥),而且它确保了一个线程更改的数据对于其它线程是可见的(更改的可见性),在释放锁之前会将对变量的修改刷新到主存中)。

4) volatile 标记的变量不会被编译器优化,禁止指令重排序;synchronized 标记的变量可以被编译器优化。

关于今天的更新就到这里啦,明天继续更新多线程之synchronized关键字,喜欢的小伙伴可以点下关注~有什么问题欢迎随时讨论哈~

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

推荐阅读更多精彩内容