并发编程-JMM
Java内存模型
一,并发编程痛点
并发编程有两个痛点问题需要解决:
- 线程通信:线程之间通过何种机制来实现数据交互
- 线程同步:线程通过何种机制来保证线程之间操作的相对顺序
二,JMM
2.1 JMM基础
JMM定义了线程和主内存之间的抽象关系:(类似于操作系统中主内存和CPU缓存和CPU的关系)
Java线程之间的通信由JMM控制
JMM定义了一套规范:
- 共享变量都存放在主内存,每个线程拥有自己的工作内存,线程和线程之间的工作内存不可以互相访问,线程之间的通信必须通过主内存
- 线程只能对自己工作内存中的变量进行操作,不能直接操作主内存的共享变量,必须先将共享变量从主内存加载到线程的工作内存
- JMM提供内存可见性的原理就是通过控制主内存和每个线程的本地内存之间的交互来实现
内存可见性: 线程之间的可见性,一个线程修改了共享变量时,两一个线程可以立马感知到这个修改后的值
2.2 重排序
定义:
- 计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。
原理:
- 流水线技术:程序在执行时候多条指令重叠进行操作,但是流水线技术最害怕中断,指令重排序是就是为了让流水线技术减少中断,将指令不按程序顺序执行,但是不改变程序执行结果
分类:
- 编译器重排序:指令重排序结果和单线程执行结果一致
- 处理器重排序:对不存在数据依赖的执行进行重排(数据依赖:后执行指令需要前面执行指令的返回结果)
- 内存系统重排序
指令重排序可以保证单线程下执行结果一致,但是不保证多线程下执行结果一致
2.3 顺序一致性模型
顺序一致性模型有两大特性:
- 一个线程中的所有操作必须按照程序的顺序(即Java代码的顺序)来执行。
- 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。即在顺序一致性模型中,每个操作必须是原子性的,且立刻对所有线程可见。
**JMM保证,只要你正确使用了同步(volatile,final,synchronized等)你就可以获取到强的顺序一致性模型,但是如果你没有正确使用同步,JMM不保证顺序一致性
happens-before模型:
JMM考虑了这两种需求,并且找到了平衡点,对编译器和处理器来说,只要不改变程序的执行结果(单线程程序和正确同步了的多线程程序),编译器和处理器怎么优化都行。
而对于程序员,JMM提供了happens-before规则(JSR-133规范),满足了程序员的需求——简单易懂,并且提供了足够强的内存可见性保证。换言之,程序员只要遵循happens-before规则,那他写的程序就能保证在JMM中具有强的内存可见性。
happens-before关系定义:
A操作happens-before B操作,意味着A操作的结果对B操作可见,且A操作执行顺序在B操作之前,但是具体的实现并不一定是按照happens-before指定的顺序,如果重排序后的执行结果和happens-before结果一致,那么JMM会允许这种重排序,所以happens-before最重要的一个证明是:A操作happens-before B操作,A操作结果对B操作可见
天然的happens-before模型:
- 程序顺序规则:一个线程中的每一个操作,happens-before于该线程的中的后序操作
- 监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
- volatile规则:对volatile变量的写,happens-before于后序对volatile变量的读
- 传递性:A volatile B ,B volatile C,则A volatile C
- start规则:线程A调用线程B的start方法,happens-before与线程B的后序操作
- join规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。