原子性是指 “不会有中间状态存在,要么什么都没改变,要么全都改变”, “对数据操作的原子性”.
在并发编程中, 原子性存在的根本原因是, 多个线程操作共享变量, 由于线程间切换调度, 导致一个线程操作了另一个线程 " 半成品" 的数据, 这是导致多线程环境下结果不可预测的一个原因.
synchronized 的原子性保证
synchronized 提供的特性是互斥, 可重入, 不可中断, 要用 synchronized 保证共享变量操作的原子性, 则需要对共享变量的读写加锁访问.
Atomic包的原子性保证, 本质是 cas操作, 处理器保证原子性
可见性是指, 一个线程的操作结果能够立刻被其他线程观察到
多核心多缓存, 导致可见性问题.
synchronized的可见性保证
依赖于synchronized实现的两条语义
volatile的可见性保证
volatile实现中的lock前缀指令, 强制刷新到内存 && 置其他缓存行 数据无效
final 变量的可见性保证 (todo)
为了填充流水线, 充分发挥指令级并行的能力, 编译器要对代码进行重排序优化, 同时处理器也有一定的乱序执行优化.
故在线程内部, 程序执行 as if serial, 语义上保持有序性, 即乱序执行的结果能够保证和顺序执行的结果一致.
然而在线程外部观察, 所有操作都是无序的(语义上无序), 这种无序我更认为是 “重排序优化” 及 “工作内存与主内存同步延迟” 的现象叠加. <深入理解JVM> - P374
volatile的有序性保证
volatile相关变量, 禁止指令重排序. 如双检锁实现中, 确保instance引用在对象未初始化成功前, 不能被其他线程观测到.
synchronized的有序性保证
看下面的双检锁实现
public class Singleton {
private static volatile Singleton instance;
private static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
看到这里我是懵逼的状态, 查看一些介绍都说synchronized 能够保证有序性, 那么为什么还要volatile ?
b乎上找到了一个很好的解释, "synchronized的有序性和volatile的有序性是从不同的角度来看待的"
线程与线程间, 每一个synchronized块可以看成是一个原子操作, 它解决了块与块之间同步延迟引发的无序