标签:java内存 结束 关于 概念 编程 das 原则 上层 软件技术
摘抄并自查
1. JMM 的介绍
线程安全:当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象就是线程安全的。
出现线程安全的问题一般是因为主内存和工作内存数据不一致和重排序导致的,而解决线程安全的问题最重要的就是理解这两种问题是怎么来的,那么,理解他们的核心在于理解 java内存模型(JMM)。
在多线程条件下,多个线程肯定会相互协作完成一件事情,一般来说会涉及到多个线程间相互通信告知彼此的状态以及当前的执行结果等,另外,为了性能优化,还会涉及到编译器指令重排序和处理器指令重排序。
2. 内存模型抽象结构
在并发编程中主要需要解决两个问题:1.线程之间如何通信;2.线程之间如何完成同步。通信是指线程之间以何种机制来交换信息,主要有两种:共享内存和消息传递。Java内存模型是共享内存的并发面模型,线程之间主要通过读-写共享变量来完成隐式通信。如果程序员不能理解Java的共享内存模型,在编写并发程序时,一定会遇到各种各样关于可见性的问题。
1)哪些是共享变量
在 java 程序中,所有的实例域,静态域和数组元素都是放在堆内存中(所有线程均可访问,是共享的),而局部变量,方法定义参数和异常处理参数不会在线程间共享。共享数据会出现线程安全的问题,而非共享数据不会出现线程安全的问题。
2)JMM 抽象结构模型
我们知道 CPU 的处理速度和主存的读写速度不是一个量级的,为了平衡这种巨大的差距,每个 CPU 都会有缓存。因此,共享变量会先放在主存中,每个线程都有属于自己的工作内存,并且会把位于主存中的共享变量拷贝到自己的工作内存,之后的读写操作均使用位于工作内存的变量副本,并在某个时刻将工作内存的变量副本写回到主存中去。JMM 就从抽血层次定义了这种方式,并且 JMM 决定了一个线程对共享变量的写入何时对其他线程是可见的。
两个线程,线程A 和 线程B 之间要完成通信的话,要经历如下两步:
从横向看,线程 A 和线程 B 就好像通过共享变量在进行隐式通信。但是,如果线程 A 更新后数据并没有及时写回主内存,而此时线程 B 读到的是过期的数据,这就出现了 “脏读” 现象。可以通过同步机制(控制不同线程间操作发生的相对顺序)来解决或者通过 volatile 关键字使得每次 volatile 变量都能够刷新到主存,从而对每个线程都是可见的。
3. 重排序
一个好的内存模型实际上会放松对处理器和编译器规则的束缚,也就是说软件技术和硬件技术都为同一个目标而奋斗:在不改变程序执行结果的前提下,尽可能提高并行度。JMM 对底层尽量减少约束,使其能够发挥自身优势。因此,在执行程序时,为了提高性能,编译器和处理器常常会对指令进行重排序。一般重排序可以分为如下三种:
这些重排序会导致线程安全的问题。
针对编译器重排序,JMM 的编译器重排序规则会禁止一些特定类型的编译器重排序,针对处理器重排序,编译器在生成指令序列的时候,会通过插入内存屏障指令来禁止某些特殊的处理器重排序。
数据依赖是什么?
如果两个操作访问同一个变量,且这两个操作有一个为写操作,此时这两个操作数就存在数据依赖性。分三种情况:读后写,写后写,写后读。这三种操作都是存在数据依赖性的,如果重排序会对最终执行结果存在影响。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序。
as-if-serial 语义?
语义的意思是:不管怎么重排序,(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守 as-if-serial 语义。as-if-serial 语义把单线程程序保护了起来,遵守 as-if-serial 语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按照程序的顺序来执行的。即,as-if-serial 语义使程序员不比担心单线程中重排序问题,也无需担心内存可见性问题。
4. happens-before 规则
上面重排序原则,比较复杂难理解,所以,JMM 为程序员在上层提供了六条规则,这样我们就可以根据规则去推论跨线程的内存可见性问题,而不用再去理解底层重排序的规则。
JSR-133 使用 happens-before 的概念来指定两个操作之间的执行顺序。由于这两个操作可以在一个线程内,也可以是在不同线程之间。因此,JMM 可以通过 happens-before 关系向程序员提供跨线程的内存可见性保证(如果 A 线程的写操作 a 与 B线程的读操作 b 之间存在 happens-before 关系,尽管 a 操作和 b 操作在不同的线程中执行,但 JMM 向程序员保证 a 操作将对 b 操作可见)。具体定义为:
上面的第一条是 JMM 对程序员的承诺。从程序员的角度来说,可以这样理解 happens-before 关系:如果 A happens-before B,那么 JMM 向程序员保证 —— A 的操作结果将对 B 可见,且 A 的执行顺序排在 B 之前。
上面的第二条是 JMM 对编译器和处理器重排序的约束原则。正如前面所说的,JMM 其实是在遵循一个基本原则:只要不改变程序的执行结果(指的单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。JMM 这么做的原因是:程序员对于这两个操作是否真的被重排序并不关心,只要执行结果不改变即可。因此,happens-before 关系本质上和 as-if-serial 语义是一回事。
比较 as-if-serial 和 happens-before :
happens-before 六项规则 :
5. 总结
一个 happens-before 规则对应于一个或多个编译器和处理器。对于Java 程序员来说, happens-before 规则简单易懂,它避免 Java 程序员为了理解 JMM 提供的内存可见性保证而去学习复杂的重排序规则及这些规则的具体实现方法。
标签:java内存 结束 关于 概念 编程 das 原则 上层 软件技术
原文地址:https://www.cnblogs.com/lili-xia/p/12486097.html