| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 并发三大特性——可见性 -> 正文阅读 |
|
[Java知识库]并发三大特性——可见性 |
引言熟悉并发的童鞋们都知道,并发编程有三大特性,分别是可见性、有序性、原子性,今天我们从一个demo中分析可见性,以及我们如何保障可见性。 JMM模型 在我们分析可见性之前,我们需要了解一个概念,就是JMM模型,也就是我们常说的java?memory?model . java虚拟机规范中定义了Java内存模型,用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现程序在各个平台上达到一致的并发效果,JMM规范了java虚拟机与计算机内存是如何协同工作的。规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。JMM描述的是一种抽象的概念,一组规则,通过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式。 ? 上代码
一、运行上述代码结果如何?????????从代码的写法上应该得到的结果是,运行一段时间后,程序会结束while?循环,并且打印出:跳出循环。但是当我们实际运行上面?结果的时候,程序会直接进入死循环,并不会结束。这就是我们常说的共享变量的可见性导致的,线程B对flag的修改,线程A并不能感知到。这样的代码在如果出现在实际业务中就会导致严重的bug。 ? ? ? ? 又JMM模型我们知道,这是因为在线程运行的时候,将flag变量都加载到了各自线程的本地内存中,而两个线程之间的通讯是通过主内存,所以我们如果想让线程B修改的变量的值,让线程A及时感知到,这就需要线程B对变量的修改及时刷新到主内存,并且其他线程本地内存对该变量的缓存失效。 二、解决方案2.1?等待我们从常理来推测,如果我们程序在短时间内不使用这个flag变量,理论上计算机会定时将变量从缓存中移除,毕竟每个线程的本地内存空间并不是很大,并且从一些常用的内存清理中间件来分析大概都是这个原理,所以我们在whil循环中增加?等待1ms,运行结果:程序跳出while?循环,达到可见性目的,这也说明计算机会将最近不是使用的变量从本地内存清除。具体等待多久才会达到这个效果和具体的硬件有关系,没有具体详细精准的时间。 2.2?线程上下文切换这钟方式典型的用法就是 Thread.yield(),让当前线程从运行态变成就绪态,交出cpu使用权。基本线程切换的时间大概为5ms-10ms之间,线程上下文切换会导致当前线程本地内存失效,当该线程再次获得CPU使用权的时候,从程序计数器中获得下一条执行的指令,并且从主内存中加载变量值,这种方式也解决了?可见性问题。 2.3 volatile关键字这种方式是我们最常见的一种解决方式,那么为什么volatile关键字可以解决这个问题呢?因为这是jdk中自带的一个关键字,所以我们需要查看jdk源码才可以更好的理解 ?从源码中我们可以发现,该关键字调用了storeload()方法,从这个方法命我们就可以知道,这里开始调用内存屏障的实现了。
以上代码中在x86处理器上的实现,在该处理其中利用 lock 实现类似内存屏障的效果,这种lock的实现方式效率更高。 lock前缀指令的作用 1. 确保后续指令执行的原子性。在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。 2. LOCK前缀指令具有类似于内存屏障的功能,禁止该指令与前面和后面的读写指令重排序。 3. LOCK前缀指令会等待它之前所有的指令完成、并且所有缓冲的写操作写回内存(也就是将store buffer中的内容写入内存)之后才开始执行,并且根据缓存一致性协议,刷新store buffer的操作会导致其他cache中的副本失效。 汇编层面的lock实现 我们可以通过增加jvm参数来观察 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp ?从验证了可见性是用来了lock指令 其余的几种方式? System.out.println(count);?底层使用了 synchronized关键字,源码最后也是调用了storeFence方法,也是利用内存屏障来实现了可见性。包括Thead.sleep()等 2.4?将count?类型从int?更换为integer这种方式其实是利用了final关键字,如果我们看过intege?源码定义的话,我们最后其实获得的是value值,而在integer中value?是通过final关键字定义的,所以说final关键字也可以解决可见性问题。 总结所以我们将上面的各种解决方案进行总结, 如何保证可见性
其实上面的这几种方式,从底层可以分文两个大的方案; ? ? ? ? 1)线程上下文切换 ? ? ? ? 2)内存屏障? ?jvm层面的 storeLoad内存屏障 === > X86 lock 替代了 mfence ? ? ? ?? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 23:51:30- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |