问题

1
2
3
4
5
6
7
8
9
10
11
12
13
private static SettingsDbHelper sInst = null;  
public static SettingsDbHelper getInstance(Context context) {
if (sInst == null) { // 1
synchronized (SettingsDbHelper.class) { // 2
SettingsDbHelper inst = sInst; // 3
if (inst == null) { // 4
inst = new SettingsDbHelper(context); // 5
sInst = inst; // 6
}
}
}
return sInst; // 7
}

Double Checked locking 模式,保证多线程安全,Coverity 是静态代码分析工具,它会模拟其实际运行情况。
原因:new SettingsDbHelper(context)并不是一个原子操作,编译后可能会因为与下面的语句发生指令重排,
既是没有下面sInst = inst,也会因为先给sInst初始化,而构造函数并没有执行完,这样在另一个线程中,调用
该函数时,确实返回了初始化的sInst(不为null),但因为构造函数没有执行完,仍然会出现线程问题。

解决方法一

在 Java 5 之后,引入扩展关键字 volatile 的功能,它能保证:对 volatile 变量的写操作,不允许和它之前的读写操作打乱顺序;对 volatile 变量的读操作,不允许和它之后的读写乱序。所以,上面的操作,只需要对 sInst 变量添加 volatile 关键字修饰即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
private static volatile SettingsDbHelper sInst = null;  
public static SettingsDbHelper getInstance(Context context) {
if (sInst == null) { // 1
synchronized (SettingsDbHelper.class) { // 2
SettingsDbHelper inst = sInst; // 3
if (inst == null) { // 4
inst = new SettingsDbHelper(context); // 5
sInst = inst; // 6
}
}
}
return sInst; // 7
}

解决方法二

对 volatile 变量的读写操作是一个比较重的操作,所以上面的代码还可以优化一下,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static volatile SettingsDbHelper sInst = null;  // <<< 这里添加了 volatile  
public static SettingsDbHelper getInstance(Context context) {
SettingsDbHelper inst = sInst; // <<< 在这里创建临时变量
if (inst == null) {
synchronized (SettingsDbHelper.class) {
inst = sInst;
if (inst == null) {
inst = new SettingsDbHelper(context);
sInst = inst;
}
}
}
return inst; // <<< 注意这里返回的是临时变量
}