单例模式总结
单例模式,最常见的有两种单例模式,饿汉式和懒汉式,如下:
/** * 饿汉式 */ public class SingletonHungry { //单例对象 private SingletonHungry instance = new SingletonHungry(); //私有构造方法 private SingletonHungry(){} public SingletonHungry getInstance(){ return instance; } }/** *懒汉式 */ public class SingletonLazy { //单例对象 private static SingletonLazy instance; //私有构造方法 private SingletonLazy(){} //静态工厂方法 public static SingletonLazy getInstance(){ if(instance==null){ instance = new SingletonLazy(); } return instance; } }上述两种方式都存在着线程不安全的情况,以懒汉式为例,instance对象还没创建的时候,如果两条线程访问getInstance方法,进入了if条件,同时都new的话,就产生了两个实例。
解决方案:synchronized加双重检测
/** *synchronized加双重检测 */ public class SingletonSyn { //单例对象 private static SingletonSyn instance; //私有构造函数 private SingletonSyn(){} //静态工厂方法 public static SingletonSyn getInstance(){ //双重检测 if(instance == null){ synchronized (SingletonSyn.class){ if(instance==null){ instance = new SingletonSyn(); } } } return instance; } }解析:双重检测的好处,就是首先判断instance为空,有两条线程都可以进入了这判断条件,当一条线程执行完了getInstance方法,释放了锁, 然后另一条线程已经进入第一层判断,这时可获取锁,如果在synchronized代码块不做判断就有产生了一个实例,就不是单例了
存在问题:此时仍难不是线程安全的,因为JVM会进行指令重排,new一个对象的步骤是1.分配对象的内存空间2.初始化对象3.设置instance指向刚分配的实例 *有些时候JVM会把其顺序条为1,3,2,3过程执行了对象就不等于null,所以就存在下边这种情况,线程A在new对象,线程B在第一个条件判断 * 当线程A先执行了1,3,此时线程B刚好判断为false,那么线程B直接返回instance,这是一个没创建好的对象,所以就出错了
解决方案:单例加上volatile关键字防止指令重排
/** *线程安全的单例代码 */ public class SingletonSyn { //单例对象 private volatile static SingletonSyn instance; //私有构造函数 private SingletonSyn(){} //静态工厂方法 public static SingletonSyn getInstance(){ //双重检测 if(instance == null){ synchronized (SingletonSyn.class){ if(instance==null){ instance = new SingletonSyn(); } } } return instance; } }存在问题:此时还会存在一个问题,我们能利用反射仍可获得多个对象
解决办法:用枚举类
/** * 枚举类实现单例模式 */ public enum SingletonEnum { INSTANCE; private SingletonEnum() { } public void print(){ System.out.println("hello world"); } } public static void main(String[] args) { SingletonEnum instance = SingletonEnum.INSTANCE; instance.print(); }至此单例一些情况全都介绍完了
