1.什么是单例模式
单例模式的想法是保证只有一个Singleton object能创建,私有的构造器和公开的getInstance()方法保证只能创建一个对象。这种方式在单线程环境下是可以走通的,但是在多线程环境下,必须要保证getInstance()这个方法是synchronization才行。
package double2.check.locking.singleton;
public class Singleton {
private boolean inUse;
private static Singleton instance;
private Singleton() {
inUse = true;
}
public static Singleton getInstance() {
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}
考虑如下情况:
1.thread 1 调用getInstance()方法,发现instance还是null
2.thread 1进入if后,想要执行2的时候被thread2打断,thread 1阻塞
3.thread 2调用getInstance()方法,发现instance还是null
4.thread 2进入if块中,创建了 Singleton 的对象 o1,并且返回了o1
5.thread 1打断thread 2,创建 Singleton的对象 o2,并且返回了o2
本来是想创建一个Singleton对象的,现在却创建了两个Singleton对象。
如果要对这个问题做修正的话,可以在getInstance()方法上加上synchronized关键字,使得某个时间段内,只有一个线程才能进入getInstance()方法。
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
但是,这样做并不好。因为每个thread去调用都要去获取锁,我们知道获取锁是要资源的;并且会影响并发性,因为每次只能有一个线程才能执行getInstance()这个方法。实际上我们只要在第一次调用这个方法获取锁就行了。
2.用synchronized 来修正
public static Singleton getInstance() {
if (instance == null) //1
synchronized(Singleton.class) { //2
instance = new Singleton();
}
return instance;
}
这样做也不行,我们得要的结果跟第一个版本是一样的。当thread 1进入if块,发现instance是null,然后进入2块试图创建Singleton对象。这时候thread 1被thread2打断,thread 2 进入if块,发现instance还是null,然后进入synchronized块,创建Singleton对象,并且返回;这时候thread 2被thread 1打断,thread 1创建对象,返回。最后发现创建了两个Singleton对象。
3.Double-checked locking
public static Singleton getInstance() {
if (instance == null)//1
synchronized(Singleton.class) {//2
if ( instance == null )//3
instance = new Singleton();//4
}
return instance;
}
thread 1,thread 2同时进入1后,考虑如下情况
1.thread 1 进入2获取Singleton锁后,执行到4
2.thread 1 被thread 2打断,thread 2开始执行
3.thread 2执行到2,发现需要获取Singleton的锁,thread 2阻塞
4.thread 1执行,创建Singleton对象,并且返回
5.thread 1被thread 2打断
6.thread 2开始执行2,这次能成功获取Singleton的锁
7.thread 2执行3发现instance现在不是空的,就马上返回了,不会再去执行4.
这次真正的只创建了一个对象。但是在现在的jvm上不能保证正确,因为新的jMM支持out-of-order-write乱序写。
4.out-of-order-write
因为有乱序写的存在,在线程1在执行4构建Singleton 对象的时候,instance可能就已经是非null了。例如:
1.thread1进入4,设置instance为非null,这时候Singleton 并没有真正的创建好。
2.thread2调用getInstance()方法,发现instance非null,之间返回Singleton 对象
3.thread1完成Singleton 的创建,并且返回。
结果thread2得到了一个并没有构建好的Singleton 对象。
5.解决方式,不用懒加载模式,直接用饥渴模式
package double2.check.locking.singleton;
public class Singleton {
private boolean inUse;
private static Singleton instance = new Singleton();
private Singleton() {
inUse = true;
}
public static Singleton getInstance() {
return instance;
}
}
或者用synchronized 方法。
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}