1.并发基础
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到
修改的值
有序性:即程序执行的顺序按照代码的逻辑顺序执行
2.java内存模型&jvm内存模型
JMM规范规定所有的变量都要在主内存中产生,而线程不允许直接操作主内存中的变量,线程需要把变量副本拷贝
到工作线程中进行操作,操作完后再回写到主内存。
主内存:JMM规定所有的变量都必须在主内存中产生。
工作内存:JVM中每个线程都有自己的工作内存,是线程私有的,可以类比CPU的高速缓存。线程的工作内存
保存了线程需要的变量在主内存中的副本。
上图可以清除看出,每个工作线程都有独立的内存空间
3.特性
可见性:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
有序性:禁止进行指令重排序。
volatile 只能保证对单次读/写的原子性,i++ 这种操作不能保证原子性
3.1 内存屏障
内存屏障是一组CPU指令,用于实现对内存操作的顺序限制。
Java编译器,会在生成指令系列时,在适当的位置会插入内存屏障来禁止处理器对指令的重新排序
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
还是有必要采用volatile的,
- 为 uniqueInstance 分配内存空间
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
JVM具有指令重排序的特性,执行顺序可能变成 1->3->2,线程T1执行 1和3 ,此时调用 #getUniqueInstance(),发现uniqueInstance不为空,但此时uniqueInstance还未初始化,导致返回值不符合要求;
参考
volatile关键字原理实现及应用