Java 并发内存模型
并发中的问题
-
指令重排
- a=1; b=x; 在没有数据依赖,所以可能会先执行b=x;a=1
-
内存可见性
- 多线程在对同一个资源进行访问的时候,会因为缓存的原因导致内存可见性问题。在多核系统中,可能核可能有自己独占的缓存。
-
原子性
synchronized关键字
进入synchronized:在进入synchronize以后,共享变量的缓存会失效,所以使用的时候需要从主内存中获取。
退出synchronized:在退出synchronize的时候,会将缓冲区(进入synchronize前或者在synchronize中修改的变量)的数据写入到主内存中。所以共享变量的修改对其他线程可见是在synchronize退出以后。
单例分析
public class Singleton {
private static Singleton instance = null;
private int v;
private Singleton() {
this.v = 3;
}
public static Singleton getInstance() {
if (instance == null) { // 1. 第一次检查
synchronized (Singleton.class) { // 2
if (instance == null) { // 3. 第二次检查
instance = new Singleton(); // 4
}
}
}
return instance;
}
}
模拟两个线程:线程A和线程B。线程A执行到instance = new Singleton()
代码。这段代码的指令可以分解为两个部分:申请内存,并且使用构造方法初始化属性;然后把对象的引用赋值给instance
。这两条只能可能会发生重新排序的。
如果线程B 这个时候执行到了if (instance == null)
那么有可能看到的instance
不是null
,因为有可能线程A在获得对象的引用之后,就马上把instance
写回了主内存,然后线程B在第一行代码又从主内存中读取了新的instance
值,发现instance
不是null
就马上返回了,但是这个时候线程B拿到的instance
对象里面的属性可能是未初始化好的。
但是如果线程A 走出了synchronized代码块,那么instance
一定是完整的内容。
volatile的使用
- 内存可见性
- 每次读取volatile变量的时候会从主内存中读取,写的时候会直接写会主内存
- 指令重排
- 单例中的
instance
赋值和构造函数的调用不会被重新排序
所以volatile的作用就非常明显了,如果我们需要多线程之间的变量具有立即可见性,就需要对变量使用volatile。
并且:由于long,double是64位的,但是java写入是32位32位的写入,所以鼓励使用volatile来进行。
网友评论