1. 工作内存和主内存
- 所有的变量都存在主内存中(一份)
- 每个线程有自己独立的工作内存(主内存中该变量的一份拷贝)
2. 可见性和共享变量
- 可见性:一个共享变量的值能够及时地被其他线程看到
- 共享变量:如果一个变量在多个线程的工作内存中都存在拷贝,那么它就是这几个线程的共享变量
下图可以反映以上说明:
3. 约束
- 线程对共享变量的操作只能在自己的工作内存中进行,不能直接修改主内存
- 不同线程之间无法访问其他线程工作内存中的变量,必须通过主内存来访问其他线程工作内存中的变量
4. 共享变量可见性的原理
线程1对工作内存1的修改能及时让线程2及时可到,就是可见了。
4.1 可见性的步骤
- 将工作内存1中更新后的值写入主内存
- 主内存将最新的共享变量的值写入工作内存2
4.2 保证可见性的必要条件
- 工作内存更新的共享变量值要及时写入主内存
- 主内存及时更新共享变量的值写入工作内存2
synchronized实现可见性
5.1 synchronized实现的内容
- 原子性(同步)
- 可见性
5.2 JMM关于synchronized 的规定
- 解锁前必须把工作内存最新值写入主内存
- 加锁前清空工作内存的共享变量值,从主内存更新最新值
5.3 synchronized互斥代码的过程
- 获得互斥锁
- 清空工作内存
- 从主内存拷贝最新变量复制到自己的工作内存
- 执行代码
- 将更改的共享变量值写入主内存
- 释放互斥锁
6 volatile实现可见性
首先在说volatile实现可见性方法之前说一下编译重排序
6.1 重排序
编译器或处理器为了提高性能做优化,调整了执行顺序使得和书写顺序不同
6.2 as if serial语义
无论如何排序,程序执行结果与代码按照书写顺序执行的结果一致。在单线程情况下,遵循as if serial语义
volatile通过内存屏障和禁止重排序优化来实现可见性
- 写操作后加入store屏障指令:写之后强行将更新的值写入主内存
- 读操作前加入load屏障指令:读之前及时从主内存更新最新值
6.3 volatile无法实现原子性
- 一般来说自增操作无法保证其原子性
解决方案:
- 同步块
- ReentrantLock(java.util.concurrent.locks) 3.AtomicInteger(java.util.concurrent.atomic)
多线程中安全使用volatile需要同时满足以下2点
- 对变量的写入操作不依赖当前值(比如自增、自减等操作;)
- 不能包含在一个不变式中(比如low<up,永远成立的)
7 synchronized和volatile的比较
- volatile不需要加锁,更加轻量级,不会阻塞线程
- 从内存可见性角度,volatile变量读等价于加锁,写等价于解锁
- synchronized可以同时保证原子性和可见性,而volatile一般来说只能保证可见性
- volatile需要注意使用的风险
- volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
- volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化 原文地址: