单例模式(java 实现 Singleton)(二)

上一篇单例模式(一)中讲述了单例模式的基本思想和原理,后来又看到不同的单例模式的实现方式,产生了疑问。
以下是网上搜到的比较具体和清晰的回答,对于单例模式的几种实现方式做了比较细致的讲解,转载来方便查阅:
单例模式在我们日常的项目中十分常见,当我们在项目中需要一个这样的一个对象,这个对象在内存中只能有一个实例,这时我们就需要用到单例。

一般说来,单例模式通常有以下几种:

1.饥汉式单例

public class Singleton {
    private Singleton(){};
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}

这是最简单的单例,这种单例最常见,也很可靠!它有个唯一的缺点就是无法完成延迟加载——即当系统还没有用到此单例时,单例就会被加载到内存中。
在这里我们可以做个这样的测试:

将上述代码修改为:

public class Singleton {
    private Singleton(){
        System.out.println("createSingleton");
    };
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
    public static void testSingleton(){
        System.out.println("CreateString");
    }
}

而我们在另外一个测试类中对它进行测试(本例所有测试都通过Junit进行测试)

public class TestSingleton {
    @Test
    public void test(){
        Singleton.testSingleton();
    }
}

输出结果:

createSingleton
CreateString    

我们可以注意到,在这个单例中,即使我们没有使用单例类,它还是被创建出来了,这当然是我们所不愿意看到的,所以也就有了以下一种单例。

2.懒汉式单例

public class Singleton1 {
    private Singleton1(){
        System.out.println("createSingleton");
    }
    private static Singleton1 instance = null;
    public static synchronized Singleton1 getInstance(){
        return instance==null?new Singleton1():instance;
    }
    public static void testSingleton(){
        System.out.println("CreateString");
    }
}

上面的单例获取实例时,是需要加上同步的,如果不加上同步,在多线程的环境中,当线程1完成新建单例操作,而在完成赋值操作之前,线程2就可能判
断instance为空,此时,线程2也将启动新建单例的操作,那么多个就出现了多个实例被新建,也就违反了我们使用单例模式的初衷了。

我们在这里也通过一个测试类,对它进行测试,最后面输出是

CreateString

可以看出,在未使用到单例类时,单例类并不会加载到内存中,只有我们需要使用到他的时候,才会进行实例化。

这种单例解决了单例的延迟加载,但是由于引入了同步的关键字,因此在多线程的环境下,所需的消耗的时间要远远大于第一种单例。我们可以通过一段测试代码来说明这个问题。

public class TestSingleton {
    @Test
    public void test(){
        long beginTime1 = System.currentTimeMillis();
        for(int i=0;i<100000;i++){
            Singleton.getInstance();            
        }
        System.out.println("单例1花费时间:"+(System.currentTimeMillis()-beginTime1));
        long beginTime2 = System.currentTimeMillis();
        for(int i=0;i<100000;i++){
            Singleton1.getInstance();            
        }
        System.out.println("单例2花费时间:"+(System.currentTimeMillis()-beginTime2));
    }
}

最后输出的是:

单例1花费时间:0
单例2花费时间:10    

可以看到,使用第一种单例耗时0ms,第二种单例耗时10ms,性能上存在明显的差异。为了使用延迟加载的功能,而导致单例的性能上存在明显差异,
是不是会得不偿失呢?是否可以找到一种更好的解决的办法呢?既可以解决延迟加载,又不至于性能损耗过多,所以,也就有了第三种单例:

3.内部类托管单例

public class Singleton2 {
    private Singleton2(){}
    private static class SingletonHolder{
        private static Singleton2 instance=new Singleton2();
    }
    private static Singleton2 getInstance(){
        return SingletonHolder.instance;
    }
}

在这个单例中,我们通过静态内部类来托管单例,当这个单例被加载时,不会初始化单例类,只有当getInstance方法被调用的时候,才会去加载
SingletonHolder,从而才会去初始化instance。并且,单例的加载是在内部类的加载的时候完成的,所以天生对线程友好,而且也不需要
synchnoized关键字,可以说是兼具了以上的两个优点。

4.总结

一般来说,上述的单例已经基本可以保证在一个系统中只会存在一个实例了,但是,仍然可能会有其他的情况,导致系统生成多个单例,请看以下情况:

public class Singleton3 implements Serializable{
    private Singleton3(){}
    private static class SingletonHolder{
        private static Singleton3 instance = new Singleton3();
    }
    public static Singleton3 getInstance(){
        return SingletonHolder.instance;
    }
}

通过一段代码来测试:

@Test
    public void test() throws Exception{
        Singleton3 s1 = null;
        Singleton3 s2 = Singleton3.getInstance();
        //1.将实例串行话到文件
        FileOutputStream fos = new FileOutputStream("singleton.txt");
        ObjectOutputStream oos =new ObjectOutputStream(fos);
        oos.writeObject(s2);
        oos.flush();
        oos.close();
        //2.从文件中读取出单例
        FileInputStream fis = new FileInputStream("singleton.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (Singleton3) ois.readObject();
        if(s1==s2){
            System.out.println("同一个实例");
        }else{
            System.out.println("不是同一个实例");
        }
    }

输出:

不是同一个实例    

可以看到当我们把单例反序列化后,生成了多个不同的单例类,此时,我们必须在原来的代码中加入readResolve()函数,来阻止它生成新的单例

public class Singleton3 implements Serializable{
    private Singleton3(){}
    private static class SingletonHolder{
        private static Singleton3 instance = new Singleton3();
    }
    public static Singleton3 getInstance(){
        return SingletonHolder.instance;
    }
    //阻止生成新的实例
    public Object readResolve(){
        return SingletonHolder.instance;
    }
}

再次测试时,就可以发现他们生成的是同一个实例了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值