1.变量写入原子性问题
long和double是64位的,当32位的机器上为这两种类型变量赋值,可能被拆分成两个32位的写操作来执行。解决办法:在long前面加上volatile关键字
2.重排序:DCL问题
单例模式的线程安全的写法不止一种,常用写法为DCL(Double Checking Locking)
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
// 此处代码有问题
instance = new Singleton();
}
}
}
return instance;
}
}
根据上一个章节的jmm模型中的重排序问题可知,会出现如下情况
- 分配一块内存。
- 在内存上初始化成员变量。
- 把instance引用指向内存。
操作2和操作3可能重排序,即先把instance指向内存,再初始化成员变量,因为二者并没有先后的依赖关系。此时,另外一个线程可能拿到一个未完全初始化的对象。这时,直接访问里面的成员变量,就可能出错。
解决办法:就是为instance变量加上volatile修饰。
3.volatile实现原理
- 在volatile写操作的前面插入一个StoreStore屏障。保证volatile写操作不会和之前的写操作重排序。
- 在volatile写操作的后面插入一个StoreLoad屏障。保证volatile写操作不会和之后的读操作重排序。
- 在volatile读操作的后面插入一个LoadLoad屏障+LoadStore屏障。保证volatile读操作不会和之后的读操作、写操作重排序
final
public class MyClass {
private int num1;
private int num2;
private static MyClass myClass;
public MyClass() {
num1 = 1; num2 = 2;
}
/*** 线程A先执行write() */
public static void write() {
myClass = new MyClass();
}
/*** 线程B接着执行write() */
public static void read() {
if (myClass != null) {
int num3 = myClass.num1;
int num4 = myClass.num2;
}
}
}
和DCL的例子类似,myClass = new MyClass()这行代码,分解成三个操作:
- 分配一块内存;
- 在内存上初始化i=1,j=2;
- 把myClass指向这块内存。
操作2和操作3可能重排序,因此线程B可能看到未正确初始化的值。
解决办法:
办法1:给num1,num2加上volatile关键字。
办法2:为read/write方法都加上synchronized关键字。
办法3:如果num1,num2只需要初始化一次,还可以使用final关键字。
网友评论