懒汉式单例一般使用的时候有这么几个步骤
- 私有化构造函数,防止使用者通过new创建
- 对new对象的代码加锁,防止多个线程new对象
- 在锁的上下各加一个判空
这个创建步骤已经深入脑髓,所以一般是信手拈来.
那么为什么不加判空的懒汉式单例不能保证线程安全?上代码
第一种写法
public static DataEntityFactory getInstance() {
//线程2等待
synchronized (DataEntityFactory.class) {
//线程1创建
INSTANCE = new DataEntityFactory();
}
return INSTANCE;
}
如果多线程获取单例,线程2被阻塞等待,线程1创建,线程1创建完成之后释放锁,那么线程2就又创建了一个对象,何谈单例?
那么一个判空能解决吗?
第二种写法
public static DataEntityFactory getInstance() {
//线程1,2分别到这里,往下走
if (INSTANCE == null) {
//线程2等待,线程1拿到锁,进入new对象阶段
synchronized (DataEntityFactory.class) {
INSTANCE = new DataEntityFactory();
}
//线程2new对象完成,释放锁
}
return INSTANCE;
}
代码注释很清楚了,当线程2释放锁之后,线程1已经进入到了第一道判空,那么剩下的情况就跟第一种写法一样了,无法保证单例
最终写法
public static DataEntityFactory getInstance() {
//线程1,2走到这里,通过判空
if (INSTANCE == null) {
//线程2等待,线程1进入new对象
synchronized (DataEntityFactory.class) {
if (INSTANCE == null) {
INSTANCE = new DataEntityFactory();
}
}
//线程1new对象结束,释放锁,线程2拿到锁
}
return INSTANCE;
}
很显然,双重判空校验解决了这个问题,线程1释放完锁之后,线程2进入new对象阶段,但是通过判空,INSTANCE已经不为null,直接返回线程1创建的实例,完事儿!
有时候觉得自己是几年撸码的老程序员,就不关心这种非常细节基础的东西,其实程序健壮与否跟基础关系非常大!这也是大厂为什么那么注重基础,我虽身在小厂,但一直拥有一颗想去大厂的心
最后附上一个很完美的单例写法(jdk1.5支持枚举之后的写法,可以防止上述懒汉式单例通过反射创建多个对象的问题)
public enum SingleInstance {
instance;
private SingleInstance() {
}
public void testSignal() {
//单例的具体方法
}
public void testSignal1() {
//单例的具体方法
}
public void testSignal2() {
//单例的具体方法
}
}