前言:说起单例模式,可能大家都熟悉,可以说是设计模式中出现频率最高的一个,为了彻底弄清单例,在这里我将说明何为单例,单例模式的演变,已经和静态类之间的区别等。
1:概念
何为单例,就是在一个应用程序中只能有一个实例,就是保证对象只能被new一次。
2:懒汉模式
懒汉我觉得这个名字很形象,就是很懒,所以别的对象加载,它就不加载,你调用我的时候我在加载。比喻hibernate中也有懒模式。ok我们开始吧
2.1:非线程安全
一天小明去面试,面试官说,你给我写个单例模式,小明一想这实在太简单了不暇思索很快写出来了如下的单例模式
1 public class Singleton { 2 private static Singleton singleton; 3 public static Singleton getSingleton() 4 { 5 if(singleton==null) 6 { 7 singleton=new Singleton(); 8 } 9 return singleton; 10 } 11 }
然后面试官一看说:你这在高并发的时候有可能会产生多个singleton实例,小明一想怎么会呢,面试官解释说,如果有2个线程T1,T2同时执行,当T1执行到第6行的时候,时间片段到,系统开始让T2执行,执行到第9行,然后T1又开始执行,因为T1已经做过判断此时并不知道singleton已经被实例化,所以singleton此时再次被实例化,这样你系统就有2个singleton对象,那还是单例吗,小明恍然大悟,这个我能解决,马上又写出下面这个
2.2:线程安全
1 public class Singleton { 2 private static Singleton singleton; 3 4 public synchronized static Singleton getSingleton() 5 { 6 if(singleton==null) 7 { 8 singleton=new Singleton(); 9 } 10 return singleton; 11 }
面试官一看,加上了线程同步,这个时候确实能保证线程安全问题,但是又提出了疑问,如果现在singleton已经被实例化了,如果10个线程同时访问,每次都要等待那么势必造成性能极大的消耗,你有没有别的方案解决问题,小明思考一分钟又写下了下面一段代码
2.3:双重校验
1 public class Singleton { 2 private static Singleton singleton; 3 4 public static Singleton getSingleton() 5 { 6 if(singleton==null) 7 { 8 synchronized (Singleton.class) { 9 if(singleton==null) 10 { 11 singleton=new Singleton(); 12 } 13 } 14 } 15 return singleton; 16 }
面试官一看,果真在上面一段代码的基础上提升了不少性能,减少了不必要的等待,但是仔细一看说你这代码有点问题,并不能保证线程的安全,小明说怎么说呢,然后面试官解释说:如果有T1,T2两个线程,T1线程运行第六行发现singleton==null,就进入第8行,开始对singleton进行实例化,因为实例化中分为三步,第一步为对象开辟内存空间,第二步为对象初始化,第三步是把这个内存地址赋给singleton,但是因为java的内存模式允许无序写入,这样一来会导致第二步和第三步位置调换,那么这样一来就坏了,如果先允许第一步和第三步了,但是此时并没有对对象进行初始化,恰恰在此时T2进入了第6行,经过判断singleton不为null,那么就会返回一个没有被初始化的对象。小明听了觉得对啊,他说我把内存模式改为不允许无序写入不就行了吗,于是就把代码修改为
1 public class Singleton { 2 private volatile static Singleton singleton;//表示有序写入 3 4 public static Singleton getSingleton() 5 { 6 if(singleton==null) 7 { 8 synchronized (Singleton.class) { 9 if(singleton==null) 10 { 11 singleton=new Singleton(); 12 } 13 } 14 } 15 return singleton; 16 }
注释1:对象实例化三步我这里做一个比喻,某个公司给员工分配一间寝室(指的就是在堆中开辟了空间)然后呢给房子进行一些标配比喻分个空调、洗衣机什么的(对象初始化),在然后呢把钥匙给到员工手里(对象进行赋值)。
3:饿汉模式
面试官又问你知道饿汉模式怎么写的吗,小明一听:哦饿汉,不就是很着急自己马上进行实例化,生怕自己无法实例化吗,这个简单马上写了一个饿汉
public class Singleton { private static Singleton singleton=new Singleton(); public static Singleton getSingleton() { return singleton; } }
面试官一看确实不错。
4:内部类模式
小明一看上面的模式,自己突发奇想,饿汉模式着急创建对象,在加载时候消耗性能,而懒汉模式又存在线程安全问题(优化后没有了)能不能结合一下呢,突然告诉面试官我还有一个比较好的方式来实现,然后他写了下面代码
1 public class Singleton { 2 private static class SingletonManager{ 3 private final static Singleton SINGLETON=new Singleton(); 4 } 5 public final static Singleton getSingleton() 6 { 7 return SingletonManager.SINGLETON; 8 }
面试官一看,不错不错,既保证了懒加载,同时也保证了线程安全问题。
5:使用场景
面试官又问小明,那么你知道使用场景吗,小明想了想说,既然在应用程序中只有一个单例,那么势必是用于共享资源,比喻数据库连接池,线程池等都可以用单例模式。
6:单例模式和静态类区别
面试官继续问:静态类同样也是产生一个对象,和单例具有高度相似你知道他们区别吗,小明回答说
1:面向对象中有三大特性继承,封装和多态,但是静态类是不可以继承的,所以从oo角度来说静态类并不符合面向对象,他们的类是不可以被覆盖,所以灵活性要比单例差的多
2:由于静态类的特殊他在编译器已经进行实例化了并不能提供懒加载模式
3:对于项目中如果进行单元测试,由于方法不能覆盖同样为测试带来了困难
4:由于静态类在编译器已经都被实例化,所以要比单例性能要快,如果只需要执行一些静态方法这个时候可以采用静态类
7:总结
单例模式看似简单,其实用起来还要考虑到很多问题,现在我把这个过程基本总结了,当然可能还有不足之处,欢迎指正。