【设计模式】03-单例模式

枚举模式:

public enum EnumSingleton {
    SINGLETON;

    EnumSingleton(){
        System.out.println("EnumSingleton被创建:"+this);
    }

    public static EnumSingleton getInstance(){
        return SINGLETON;
    }
}

它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

饿汉模式:

public class HungrySingleton {

    private HungrySingleton(){
        System.out.println("HungrySingleton被创建:"+this);
    }

    private static final HungrySingleton SINGLETON=new HungrySingleton();

    public static HungrySingleton getInstance(){
        return SINGLETON;
    }
}

类加载时创建单例对象,线程安全。

静态内部类模式:

public class InnerClassSingleton {

    private InnerClassSingleton(){
        System.out.println("InnerClassSingleton被创建:"+this);
    }

    private static class InnerClassHolder{
        private static final InnerClassSingleton instance=new InnerClassSingleton();
    }

    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,而这种方式是Singleton类被装载了,instance不一定被初始化。因为InnerClassHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。

双检锁模式:

public class LazyDoubleCheckSingleton {

    private LazyDoubleCheckSingleton(){
        System.out.println("LazyDoubleCheckSingleton被创建:"+this);
    }

    private static volatile LazyDoubleCheckSingleton instance;

    public static LazyDoubleCheckSingleton getInstance(){
        if(instance==null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(instance==null){
                    instance=new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

懒加载+线程安全与静态内部类模式有同样的效果。

懒加载同步模式:

public class LazySyncSingleton {

    private LazySyncSingleton(){
        System.out.println("LazySyncSingleton被创建:"+this);
    }

    private static LazySyncSingleton instance;

    public synchronized static LazySyncSingleton getInstance(){
        if(instance==null){
            instance=new LazySyncSingleton();
        }
        return instance;
    }
}

由于调用getInstance方法就会走synchronized同步代码快性能较双检锁差。

下面测试一下以上几种单例模式的获取速度。

private static void testGetSingletonSpeed(){
        //5千万
        int count=50000000;
        long start=System.currentTimeMillis();
        for(int i=0;i<count;i++){
            EnumSingleton.getInstance();
        }
        System.out.println("创建EnumSingleton用时:"+(System.currentTimeMillis()-start));

        start=System.currentTimeMillis();
        for(int i=0;i<count;i++){
            HungrySingleton.getInstance();
        }
        System.out.println("创建HungrySingleton用时:"+(System.currentTimeMillis()-start));

        start=System.currentTimeMillis();
        for(int i=0;i<count;i++){
            InnerClassSingleton.getInstance();
        }
        System.out.println("创建InnerClassSingleton用时:"+(System.currentTimeMillis()-start));

        start=System.currentTimeMillis();
        for(int i=0;i<count;i++){
            LazyDoubleCheckSingleton.getInstance();
        }
        System.out.println("创建LazyDoubleCheckSingleton用时:"+(System.currentTimeMillis()-start));

        start=System.currentTimeMillis();
        for(int i=0;i<count;i++){
            LazySyncSingleton.getInstance();
        }
        System.out.println("LazySyncSingleton:"+(System.currentTimeMillis()-start));
    }

每种模式分别调用5千万次getInstance()方法结果如下:

EnumSingleton被创建:SINGLETON
创建EnumSingleton用时:7
HungrySingleton被创建:com.pattern.singleton.HungrySingleton@3a4afd8d
创建HungrySingleton用时:6
InnerClassSingleton被创建:com.pattern.singleton.InnerClassSingleton@555590
创建InnerClassSingleton用时:7
LazyDoubleCheckSingleton被创建:com.pattern.singleton.LazyDoubleCheckSingleton@424c0bc4
创建LazyDoubleCheckSingleton用时:14
LazySyncSingleton被创建:com.pattern.singleton.LazySyncSingleton@16b4a017
LazySyncSingleton:1011

执行了几次结果都很接近。

上面的几种单例模式在以下两种情况下会有问题:

1.通过反射获取私有构造方法创建对象,以静态内部类模式和饿汉模式举例。

private static void testReflectSingleton() throws Exception {
        Class<InnerClassSingleton> aClass = InnerClassSingleton.class;
        Constructor<InnerClassSingleton> constructor = aClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerClassSingleton instance = constructor.newInstance();
        System.out.println(instance==InnerClassSingleton.getInstance());

        Class<HungrySingleton> aClass2 = HungrySingleton.class;
        Constructor<HungrySingleton> constructor2 = aClass2.getDeclaredConstructor();
        constructor2.setAccessible(true);
        HungrySingleton instance2 = constructor2.newInstance();
        System.out.println(instance2==HungrySingleton.getInstance());
    }

执行结果如下:

InnerClassSingleton被创建:com.pattern.singleton.InnerClassSingleton@60e53b93
InnerClassSingleton被创建:com.pattern.singleton.InnerClassSingleton@5e2de80c
false
HungrySingleton被创建:com.pattern.singleton.HungrySingleton@1d44bcfa
HungrySingleton被创建:com.pattern.singleton.HungrySingleton@266474c2
false

说明通过[类名.class]方式获取字节码对象的方式获取构造方法可以不触发类加载创建对象,而通过getInstance()方法会触发类加载机制为静态变量负值又创建了一个对象,这两个对象不同破坏了单例模式。

注:类名.class不会触发类加载机制,而Class.forName()会。

解决方式就是在类中加入一个boolean变量,私有构造方法中同步boolean变量,若为true则代表创建过了抛出异常否则boolean设置为true。代码如下

public class InnerClassSingleton {

    private static boolean initalized = false;

    private InnerClassSingleton(){
        synchronized(InnerClassSingleton.class){
            if(initalized){
                throw new RuntimeException("不能重复创建单例");
            }else{
                initalized=true;
            }
        }
        System.out.println("InnerClassSingleton被创建:"+this);
    }

    private static class InnerClassHolder{
        private static final InnerClassSingleton INSTANCE=new InnerClassSingleton();
    }

    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.INSTANCE;
    }
}

继续运行testReflectSingleton测试方法,结果如下:

InnerClassSingleton被创建:com.pattern.singleton.InnerClassSingleton@60e53b93
Exception in thread "main" java.lang.ExceptionInInitializerError
	at com.pattern.singleton.InnerClassSingleton.getInstance(InnerClassSingleton.java:29)
	at com.pattern.singleton.test.SingletonTest.testReflectSingleton(SingletonTest.java:26)
	at com.pattern.singleton.test.SingletonTest.main(SingletonTest.java:16)
Caused by: java.lang.RuntimeException: 不能重复创建单例
	at com.pattern.singleton.InnerClassSingleton.<init>(InnerClassSingleton.java:16)
	at com.pattern.singleton.InnerClassSingleton.<init>(InnerClassSingleton.java:9)
	at com.pattern.singleton.InnerClassSingleton$InnerClassHolder.<clinit>(InnerClassSingleton.java:25)
	... 3 more

成功防止创建了第二个对象。但也有个弊端,一旦通过反射创建第一个对象,调用getInstance就会抛出ExceptionInInitializerError,失去了getInstance()方法的意义。

2.序列化与反序列化破坏单例

我们将上述单例类全部实现Serializable接口然后测试下面代码

private static void testSerializable() throws Exception {
        EnumSingleton instance = EnumSingleton.getInstance();
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bos);
        oos.writeObject(instance);
        System.out.println("EnumSingleton序列化内容:"+bos.toString());
        ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bis);
        Object readObject = ois.readObject();
        System.out.println(instance==readObject);

        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        ByteArrayOutputStream bos2=new ByteArrayOutputStream();
        ObjectOutputStream oos2=new ObjectOutputStream(bos2);
        oos2.writeObject(hungrySingleton);
        System.out.println("HungrySingleton:"+bos2.toString());
        ByteArrayInputStream bis2=new ByteArrayInputStream(bos2.toByteArray());
        ObjectInputStream ois2=new ObjectInputStream(bis2);
        Object readObject2 = ois2.readObject();
        System.out.println(hungrySingleton==readObject2);
    }

运行结果如下:

EnumSingleton被创建:SINGLETON
EnumSingleton序列化内容:��~r#com.pattern.singleton.EnumSingletonxrjava.lang.Enumxpt	SINGLETON
true
HungrySingleton被创建:com.pattern.singleton.HungrySingleton@10f87f48
HungrySingleton:��sr%com.pattern.singleton.HungrySingleton�)��7]xp
false

可以看出枚举类型通过序列化和反序列化后仍是单例,而其余单例类已经破坏了单例对象,要解决HungrySingleton保持单例状态的方法就是加入一个私有方法readResolve()并返回类中的已有的静态单例对象。

注:原因分析请《ObjectOutputStream序列化

public class HungrySingleton implements Serializable{
    private static boolean initalized = false;
    private HungrySingleton(){
        synchronized(HungrySingleton.class){
            if(initalized){
                throw new RuntimeException("不能重复创建单例");
            }else{
                initalized=true;
            }
        }
        System.out.println("HungrySingleton被创建:"+this);
    }
    private static final HungrySingleton SINGLETON=new HungrySingleton();
    public static HungrySingleton getInstance(){
        return SINGLETON;
    }
    //加入此方法防止反序列化破坏单例
    private  Object readResolve(){
        return  SINGLETON;
    }

}

在次运行上面的测试,结果如下:

EnumSingleton被创建:SINGLETON
EnumSingleton序列化内容:��~r#com.pattern.singleton.EnumSingletonxrjava.lang.Enumxpt	SINGLETON
true
HungrySingleton被创建:com.pattern.singleton.HungrySingleton@10f87f48
HungrySingleton:��sr%com.pattern.singleton.HungrySingleton�)��7]xp
true

最后说一种Spring容器单例模式的方法

public class BeanFactory {
    private BeanFactory(){}
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
    public static Object getBean(String className){
        if(!ioc.containsKey(className)){
            Object obj = null;
            try {
                obj = Class.forName(className).newInstance();
                ioc.put(className,obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        }else{
            return ioc.get(className);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值