0%

安全的单例模式

双重检查模式

以双重检查模式为例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static Singleton singleton;
public static Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}

最常见的单例实现,但是这个单例实现有三个问题

问题一

虽然已经使用synchronized进行同步,但在第4步创建对象时,会有下面的伪代码:

1
2
3
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间

当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,2、3步会被禁止重排序

问题二

这种代码不能防止被直接创建
所以要添加私有构造函数

问题三

即使构造函数私有化,也不能防止被反射攻击
比如

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) throws Exception{
Singleton s1=Singleton.getSingleton();
Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s2=constructor.newInstance();
System.out.println(s1==s2);
}
}

输出为false
其中constructor.setAccessible(true);调用方法或者产生初始化对象实例的时候会践行权限检查
,这个问题解决思路就是防止私有的构造方法被创建两次

1
2
3
4
5
6
7
8
9
10
private static boolean flag=true;
private Singleton(){
synchronized (Singleton.class){
if(flag){
flag=false;
}else {
throw new RuntimeException("对象已存在");
}
}
}

再一次使用反射代码测试,输出结果

1
2
3
4
5
6
7
8
9
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at test.test.Test.main(Test.java:10)
Caused by: java.lang.RuntimeException: 对象已存在
at test.test.Singleton.<init>(Singleton.java:11)
... 5 more

完整双重检查模式代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton {
private static volatile Singleton singleton;
private static boolean flag=true;
private Singleton(){
synchronized (Singleton.class){
if(flag){
flag=false;
}else {
throw new RuntimeException("对象已存在");
}
}
}
public static Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}