并发编程之内存可见性
一、如何保证共享变量的可见性
- volatile
- 内存屏障 StoreStore。
- volatile执行写操作时,会在写操作后加入一条store屏障指令
- volatile执行读操作时,会在读操作后加入一条load屏障指令
- 禁止重排序
写volatile变量的过程:
- 改变线程工作内存中volatile变量副本的值
- 将改变后的副本的值从工作内存刷新到主内存
读volatile变量的过程:
3. 从主内存中读取volatile变量的最新值到线程的工作内存
4. 从工作内存中读取volatile变量的副本
- synchronized
- 在线程解锁前将工作内存中的值刷新至主内存
- 在线程加锁前清空工作内存,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁需要的是同一把锁)
- 获得互斥锁
- 清空工作内存
- 从主内存中拷贝共享变量的副本到工作内存
- 执行代码
- 将共享变量刷新回主内存
- 释放互斥锁
二、从number++看原子操作
number++不是原子操作,可细分为三步:
- 读取number的值
- 将number的值+1
- 写入最新的number的值
synchronized(this){
number++;
}
Lock lock = new Reetrantlock();
lock.lock();
try
number++;
加入synchronized,将对number变量进行加锁,在当前线程执行完之前,其他线程无法对number进行操作。变为原子操作。
变量为volatile变量,无法保证原子性
三、volatile适用场合
- 对变量的写入操作不依赖其当前值
- 不满足:number++、count= count+5等
- 满足:boolean变量、记录温度变化的变量等
- 该变量没有包含在具有其他变量的不变式中
- low<up
四、重排序
- 编译器优化的重排序(编译器重排序)
- 指令并行重排序(处理器重排序)
- 内存系统的重排序(处理器重排序)
线程之间的交叉执行会破坏原子性
五、as-if-serial原则
无论如何进行重排序,程序执行的结果都应该与代码顺序执行的结果一致。(Java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)
六、volatiele 和synchronized比较
- volatile不需要加锁,比synchronized更加轻量级,不会阻塞线程。
- 从内存可见性来讲。volatile读相当于加锁,volatile写相当于解锁
- synchronized既能保证可见性,又能够保证原子性。volatile仅能保证可见性,但无法保证原子性
- final也可以保证内存的可见性(一旦赋值就无法更改)
七、64位变量(long,double)
对64位变量(long或者double)的读写可能不是原子操作
java内存模型允许jvm将没有被volatile修饰的64位数据类型的读写操作划分为两次对32位的读写操作来进行