当前位置: 首页 > news >正文

网站系统有哪些app企业网站模板

网站系统有哪些,app企业网站模板,免费行情网站app下载大全,石家庄好用的招聘网站java内存模型的理解并发问题产生的源头缓存导致的可见性问题线程切换导致的原子性问题编译优化带来的有序性问题小结Java内存模型: 解决可见性和有序性问题Java内存模型与JVM内存模型的区别volatile关键字Happens-Before规则小结思考题参考并发问题产生的源头 缓存导致的可见性…

java内存模型的理解

  • 并发问题产生的源头
    • 缓存导致的可见性问题
    • 线程切换导致的原子性问题
    • 编译优化带来的有序性问题
    • 小结
  • Java内存模型: 解决可见性和有序性问题
    • Java内存模型与JVM内存模型的区别
    • volatile关键字
    • Happens-Before规则
    • 小结
    • 思考题
  • 参考


并发问题产生的源头

缓存导致的可见性问题

可见性定义: 一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。

在单核CPU时代,所有线程都是被同一个CPU调度,因此共享同一块cpu的缓存,不存在缓存一致性问题:
在这里插入图片描述
多核CPU时代,每个线程都有可能同时被不同的cpu调度,每个cpu都有各自的缓存,并且读写优先走缓存,这就会导致缓存一致性问题:

在这里插入图片描述
高速缓存和主内存之间如何保持数据一致性


线程切换导致的原子性问题

CPU能保证的原子操作是CPU指令级别的,高级语言里一条语句往往需要多条 CPU 指令完成,例如:count += 1,至少需要三条 CPU 指令:
在这里插入图片描述


编译优化带来的有序性问题

有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序,从而导致意外的bug。

最常见的例子就是双重锁检查创建单例对象了:

public class Singleton {static Singleton instance;static Singleton getInstance(){if (instance == null) {synchronized(Singleton.class) {if (instance == null)instance = new Singleton();}}return instance;}
}

new创建对象在java中分为三步:

  • 分配一块内存 M;
  • 在内存 M 上初始化 Singleton 对象;
  • 然后 M 的地址赋值给 instance 变量。

但是如果编译器进行了指令重排序优化,变成了下面这样:

  • 分配一块内存 M;
  • 将 M 的地址赋值给 instance 变量;
  • 最后在内存 M 上初始化 Singleton 对象。

在下面场景中,线程B访问未初始化过的instance,可能会触发空指针异常:
在这里插入图片描述


小结

java并发编程问题三大根本来源: 可见性,原子性,有序性

  • 缓存导致的可见性问题
  • 线程切换导致的原子性问题
  • 编译优化导致的有序性问题

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

Java内存模型与JVM内存模型的区别

  1. Java内存模型定义了一套规范,能使JVM按需禁用cpu缓存和禁止编译优化。这套规范包括对volatile, synchronized, final三个关键字的解析,和7个Happen-Before规则。

  2. JVM内存模型是指程序计数器,虚拟机栈,本地方法栈,堆,方法区这5和要素。


volatile关键字

volatile在c语言中最原始的含义就是禁用cpu缓存,volatile修饰符表达的是: 对某个变量的读写,不能使用cpu缓存,必须从内存中读取或者写入。

大家看下面这个例子: 线程A执行writer方法,按照volatile语义,会把变量v=true写入内存,假设线程B执行reader方法,同样按照volatile语义,线程B会从内存中读取变量v, 如果线程B看到v==true时,那么线程B看到的变量x的值是多少呢?

class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 42;v = true;}public void reader() {if (v == true) {// 这里x会是多少呢?}}
}

答案:

  • jdk 1.5之前,x可能是42,也可能是0 ,因为变量x可能被cpu缓存而导致可见性问题
  • jdk 1.5之后, x就是等于42。因为java内存模型在1.5版本对volatile语义进行了增强

怎么增强的呢?

  • happens-before规则

Happens-Before规则

Happens-Before规则:前面一个操作的结果对后续操作是可见的;

Happens-Before规则约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守Happens-Before规则。

具体规则如下:

  1. 程序的顺序性规则:在一个线程中,前面的操作Happens-Before于后续的任意操作。
  2. volatile变量规则:对一个volatile变量的写操作Happens-Before于对这个volatile变量的读操作。
  3. 传递性规则:A Happens-Before B,B Happens-Before C,那么A Happens-Before C。
    在这里插入图片描述

从图中,我们可以看到:

  • “x=42” Happens-Before 写变量 “v=true” ,这是规则 1 的内容;
  • 写变量“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 语义来搞定可见性的。

  1. 管程中锁的规则:synchronized是Java对管程的实现,隐式加锁、释放锁,对一个锁的解锁Happens-Before于后续对这个锁的加锁。
  2. 线程start()规则:主线程A启动子线程B后,start()操作 Happens-Before于子操作中的任意操作。
  3. 线程join规则:在线程A中调用线程B的join()并返回,线程B中的任意操作 Happens-Before于join()的返回。
  4. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  5. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

上述很多规则都需要配合传递性规则进行理解。


小结

Java内存模型涉及的几个关键词:锁、volatile字段、final修饰符与对象的安全发布。

  • 第一是锁,锁操作是具备happens-before关系的,解锁操作happens-before之后对同一把锁的加锁操作。实际上,在解锁的时候,JVM需要强制刷新缓存,使得当前线程所修改的内存对其他线程可见。
  • 第二是volatile字段,volatile字段可以看成是一种不保证原子性的同步但保证可见性的特性,其性能往往是优于锁操作的。但是,频繁地访问 volatile字段也会出现因为不断地强制刷新缓存而影响程序的性能的问题。
  • 第三是final修饰符,final修饰的实例字段则是涉及到新建对象的发布问题。当一个对象包含final修饰的实例字段时,其他线程能够看到已经初始化的final实例字段,这是安全的。

Java内存模型底层怎么实现的?

  • 主要是通过内存屏障(memory barrier)禁止重排序的,即时编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令。对于编译器而言,内存屏障将限制它所能做的重排序优化。而对于处理器而言,内存屏障将会导致缓存的刷新操作。比如,对于volatile,编译器将在volatile字段的读写操作前后各插入一些内存屏障。

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


思考题

还是文中给出的案例,大家思考会不会产生x=42和v=true重排序,导致线程B读取到x=0的结果呢?

class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 42;v = true;}public void reader() {if (v == true) {// 这里x会是多少呢?}}}

解答:

  • 程序顺序性规则是针对单线程的。如果只考虑单线程,那么编译器可以对范例代码进行指令重排优化。但是对于多线程,volatile 变量规则、传递性这 2 条规则,就附加了新的限制。对于多线程,这 3 条 happens-before 规则要求,线程 B 在读到 v = true 的时候,也能见到 x = 42。如果编译器仍然按照单线程的情况,对这两条语句进行指令重排,把 v = true 放到 x = 42 之前。那么,线程 B 就有可能看不到 x 的值为 42。这显然违背了 happens-before 的规定。编译器为了符合规则,只能不进行指令重排优化了。
  • 为了符合 happens-before 规定,对于示例代码,编译器不能进行指令重排的编译优化。但实际上,仅仅不进行指令重排编译优化,并不能保证编译后的代码的执行结果,符合 happnes-before 规定。因为指令重排这一优化措施,并不仅仅是编译器会做。现代 cpu 在执行机器指令的时候,同样会做指令重排的优化。所以,如果 cpu 在执行机器指令时,发生了机器指令的重排序。上述实例代码的结果,仍然有机会不符合 happens-before 规定。
  • 为了令到编译后的代码的执行结果,能够符合 happens-before 规定。编译器除了不能做指令重排序编译优化之外,还要在生成的机器代码中。加入特定 cpu 指令,令到 cpu 只会执行完这条特定指令之后,才会执行后续的其它机器指令。而这种特定指令,是通过建立 “内存屏障” 来禁止 cpu 的指令重排序的。那为什么叫 “内存屏障” 呢?可以理解为一种特殊指令,要求 cpu 把缓存数据写回到主内存中。这就像在内存中建立了一道屏障,令到后面的代码不能越过屏障,提前执行。

jmm 是一个规范,它用于指导编译器的行为。但它本身不会限制编译器所使用的具体编译技术。所以,在 jmm 规范中,不会提到 “指令重排” 或者 "内存屏障” 这些具体的实现技术。这是我们在学习规范类知识的时候,需要注意的。


参考

JAVA并发编程实战

http://www.yayakq.cn/news/157498/

相关文章:

  • 为什么百度搜索不到我的网站用c语言做网站
  • 筹划建设智慧海洋门户网站平面设计学生作品集
  • 优化网站排名推广中国电建成都设计院
  • 如何提高网站安全自己做soho需要做网站吗
  • 如何建设企业网站策划品牌全案
  • 网站建好以后每年都续费么wordpress 多人编辑器
  • 如何建手机销售网站怎么做小说推广挣钱
  • 上虞区住房和城乡建设局网站广告设计制作工作室
  • 网页和网站设计asp.net做网站的步骤
  • 什么软件可以刷网站排名太原seo建站
  • 如何做自助搜券网站湖南3合1网站建设
  • 阿里云腾讯云网站建设网站建设 微信微博外包
  • 网站域名设计方案html网页模板怎么使用
  • 福建省建设职业注册资格管理中心网站网页制作实训内容
  • 0基础多久学会网站架构wordpress无法批量管理
  • wordpress版权图片乐陵seo网站优化
  • 石家庄网站建设汉狮怎么样安全的网站建设服务
  • 如何建立自己网站视频教程怎么样才能把网站关键词做有排名
  • 鄂州市网站设计网站的关键点
  • 淘宝网站如何在邮件里做超链接克隆网站后怎么做
  • 用Off做网站高校网站平台建设
  • 网站推广排名收费wordpress自动保存
  • 成都模板网站建设服务做英文网站 是每个单词首字母大写 还是每段落首字母大写
  • 怎样为企业设计网站网站建设要注意些什么
  • jsp网站开发小程序海外网站seo
  • app开发公司的联系方式苏州首页排名关键词优化
  • 咸阳 网站建设wordpress单位内网做网站
  • 黑龙江网站建站建设用ps做网站得多大像素
  • 资讯类网站建设wordpress 双语主题
  • 广东移动网站seo是什么专业的课程