Java内存模型
Java内存模型
概述
多任务是被证明的有效的压榨处理器能力的方式
一个服务端为多个客户端提供服务是常见的场景,并发协调是否有效大大影响了程序的效率
Java语言和JVM提供了很多工具大大降低了并发编程的门槛。但程序员不能过度依赖这些语言和框架,才能真正利用好这些工具
服务器常用的衡量性能量化指标:TPS, Transactions Per Second; 数据库常用的衡量性能量化指标:QPS, Queries Per Second
硬件效率与一致性
学习Java的并发,可以通过物理计算机中的并发问题找到相似点,有很大的参考意义。
- 让计算机并发->充分利用计算机的性能,值得商榷 大多数运算任务,不止是依靠处理器,还需要与内存交互
存储与CPU性能的差距,需要在中间加一层高速缓存(Cache),作为内存与处理器之间的缓冲。
Cache引入问题:Cache Coherence,缓存一致性。每个处理器都有自己的高速缓存,却同享同一个主存。
此处应该有图:处理器,高速缓存,主内存的关系
- 除了高速缓存的问题,还有指令乱序执行 目的是为了内部计算单元能够尽量被充分利用
Java内存模型
JMM, Java Memory Model, 用于屏蔽各种硬件和操作系统的内存访问差异,达到多平台的一致性;C/C++直接使用物理硬件和操作系统的内存模型
在JMM的设计上,必须足够严谨用于保证正确性,也需要保证足够宽松,能够更好地使用硬件的各种特性。
主内存和工作内存
JMM的目标定义各个变量的访问规则:存取变量的细节
局部变量,方法参数都是线程私有,不会被共享,不会出现竞争问题
主内存类比物理机主内存;工作内存类比处理器告诉缓存。Working Memory 保存了变量的主内存副本,实际上是拷贝的引用和对象在某个线程中访问到的字段
不同线程的工作内存是独立的,这就是容易出现问题的地方
不准确的对应。
- 与JVM对应:主内存->Java堆中的实例数据部分;工作内存->虚拟机栈中的部分区域
- 与物理机对应:主内存->硬件内存;JVM则会将Working Memory 放在寄存器和高速缓存中
内存间的交互
交互的操作都可以视为是原子性的。除了double和long在某些平台有例外
交互操作:
- lock, unlock : 作用于主内存变量,标示线程的独占状态
- 作用于主内存:read, write
- 作用于工作内存:load, store。与执行引擎相关:use, assign
交互原则
略微繁琐,可以使用“先行发生”原则来等效保证
对于 volatile 型变量的特殊规则
最轻量级的同步机制
voatile 的变量具备两种特性:
- 保证对于其他线程的可见性:新值对于其他线程是立即可知的
- 禁止指令重排
值得注意的问题,由于 Java 里面的运算并非原子操作,导致操作栈栈顶的值是过期数据。
如果要安全使用voatile,必须保证下面条件:(核心就是没有依赖)
- 运算结果不依赖变量的当前值(比如直接赋值),单一线程访问(这就是废话)
- 变量不需要与其他的状态变量共同参与不变约束。
可以用于单例模式的创建。如果不适用voatile,则可以用以下代码:
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
Singleton tmp = instance;
if (tmp == null) {
synchronized(Singlet.class) {
tmp = new Singleton();
}
instance = tmp;
}
}
}
return instance;
}
优化版本:
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
Singleton tmp = instance;
if (tmp == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
}
}
return instance;
}