并发编程中你们最熟悉的应该是volatile和synchronized了,使用最多的场景应该是单例吧。看代码,你以为下边这个单例有问题吗? 图1:java
public class UserManager{
private static UserManager sUserManager;
private UserManager(){}
public static UserManager getInstance(){
if(sUserManager == null){//1
synchronized (UserManager.class){
if(sUserManager == null){
sUserManager = new UserManager();
}
}
}
return sUserManager;
}
}
复制代码
乍一看好像没什么毛病,有点经验的人仔细再一看,诶发现少了一个volatile。标准的写法好像应该长下边这样:
图2:编程
public class UserManager{
private static volatile UserManager sUserManager;
private UserManager(){}
public static UserManager getInstance(){
if(sUserManager == null){//2
synchronized (UserManager.class){
if(sUserManager == null){
sUserManager = new UserManager();// 3
}
}
}
return sUserManager;
}
}
复制代码
那问题就来了,加这个volatile有什么用?不是已经加了synchronized了吗?不加不行吗? 看过不少文章,模糊记得volatile是用来保证可见性的。并发
一个线程写共享变量,在另一个线程中能当即可见。好比图2(在volatile关键字下)线程A执行完3位置的代码后,这个时候线程B开始进行2位置的条件判断,就能立马看见sUserManager的值不为null。若是如图1(没有volatile加持),并不能保证这种可见性。那你可能会想synchronized难道不保证可见性吗?答案是:正确使用synchronized同步是能够保证可见性的。
以下图3:优化
public class UserManager{
private static UserManager sUserManager;
private UserManager(){}
public synchronized static UserManager getInstance(){//4
if(sUserManager == null){
sUserManager = new UserManager();
}
return sUserManager;
}
}
复制代码
全部共享变量的访问必须由synchronized关键字同步。 同一个对象上,synchronized代码块能保证访问共享变量,是其余线程离开synchronized同步代码的操做结果,也就保证了可见性。 图1中1位置代码并无synchronized同步,那么访问这个地方的代码就多是失效的。图3中对sUserManager变量进行了正确的同步。spa
其实可见性并非图1单例写法的症结。图1问题重点在于访问到位置1代码的时候,sUserManager不为null,可是UserManager尚未初始化完成。出现这种状况在于编译优化的时候代码可能会重排序。
如图4线程
public class UserManager{
private static UserManager sUserManager;
private String name;//6
private int age;//7
private UserManager(){
name = "聪聪";
age = 1;
}
public static UserManager getInstance(){
if(sUserManager == null){//8
synchronized (UserManager.class){
if(sUserManager == null){
sUserManager = new UserManager();// 5
}
}
}
return sUserManager;
}
}
复制代码
线程A执行位置5代码后,按照直觉,应该位置6位置7代码已经执行完毕。事实上,通过编译优化后,线程A执行过位置5代码后,线程B在代码8位置处可能看到了sUserManager不为null,可是name和age还没初始化,这个时候sUserManager是个无效的值。1> 因为volatile可以禁止对共享变量的代码重排,因此图2中的单例是正确的。2> 正确的同步(全部共享变量的访问必须由synchronized关键字同步。)并不能禁止代码重排,可是能防止访问到失效的值,因此图3是正确的。code