单例模式:层层剖析寻找最高效安全的单例


问题来源

  什么是单例?它的运用场景是什么?

  单例模式是指保证在系统中只存在某类唯一对象。运用场景随处可见,例如工具类、Spring容器默认new对象等。

  单例模式有几种实现方式?

  饿汉式、懒汉式、双重检查锁式、内部类式、枚举式。

  推荐使用方式?

  饿汉式、内部类式。

饿汉式

  饿汉式顾名思义饿,那么当应用程序一开始类加载,类的对象立马实例化加载至JVM。

 1 public class SingletonClass {
 2     /**
 3      * 优点:调用效率高。
 4      * 缺点:没有延迟加载。
 5      */
 6     private static SingletonClass instance =new SingletonClass();
 7     
 8     public static SingletonClass getInstance(){
 9         return instance;
10     }
11 }

  为什么调用效率高?没有延迟加载?

  答:假设在高并发的场景下,有10W+并发调用,不需要同步处理。可以直接在堆内存直接获取对象不需要任何等待。

    同样,它没有延迟加载,如果它是需要消耗很大内存的对象,最开始就加载入堆内存,而用户暂时不需要。这样就会严重占用堆内存,影响运行效率。

懒汉式

  导引:脑洞大开的程序员们说:上述问题还不简单,当调用的时候在new对象不就行。于是出现了懒汉式的雏形版本。

public class SingletonClass {
    private static SingletonClass instance;
    
    public static SingletonClass getInstance(){
        if(null==instance){
            instance=new SingletonClass();
        }
        return instance;
    }
}

  懒汉式顾名思义懒,就是延迟加载,当被调用的时候再实例化。

  问题:如果你是初出茅庐的应届生写成这样,估计面试官也不会追究什么。如果你是有一年工作年限的程序员,估计面试官就会声讨你了。假设,并发数10W+,它就将被蹂躏的不堪入目。那么我们需要怎么解决呢?加上同步操作就大功告成。

 1 public class SingletonClass {
 2     
 3     //调用效率低、延迟加载
 4     private static SingletonClass instance;
 5     
 6     public static synchronized SingletonClass getInstance(){
 7         if(null==instance){
 8             instance=new SingletonClass();
 9         }
10         return instance;
11     }
12 }

  问题:从效率维度考虑,估计这样已经完美了吧?但是,从安全纬度考虑,依然隐隐约约存在问题。如果是接触过反射、反序列化的同学,我们一起来继续探讨。

/**
 * 通过反射破坏懒汉式单例
 * @author aaron
 */
public class Client {
    public static void main(String[] args) throws Exception {
        SingletonClass clazzOne=SingletonClass.getInstance();
        SingletonClass clazzTwo=SingletonClass.getInstance();
        
        System.out.println("clazzOne-hasCode:"+clazzOne.hashCode());
        System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode());
        
        Class<SingletonClass> clazz=(Class<SingletonClass>)Class.forName("singleton.SingletonClass");
        Constructor<SingletonClass> c=clazz.getConstructor(null);
        c.setAccessible(true);
        SingletonClass clazzThree=c.newInstance();
        SingletonClass clazzFour=c.newInstance();
        System.out.println("clazzThree-hasCode:"+clazzThree.hashCode());
        System.out.println("clazzFour-hasCode:"+clazzFour.hashCode());
    }
}

 1 public class SingletonClass implements Serializable{
 2     
 3     private static SingletonClass instance;
 4     
 5     public static synchronized SingletonClass getInstance(){
 6         if(null==instance){
 7             instance=new SingletonClass();
 8         }
 9         return instance;
10     }
11     
12     public static void main(String[] args) throws Exception {
13         SingletonClass clazzOne=SingletonClass.getInstance();
14         SingletonClass clazzTwo=SingletonClass.getInstance();    
15         System.out.println("clazzOne-hasCode:"+clazzOne.hashCode());
16         System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode());
17         
18     
19         FileOutputStream fos=new FileOutputStream(new File("f:/test.txt"));
20         ObjectOutputStream bos=new ObjectOutputStream(fos);
21         bos.writeObject(clazzOne);
22         bos.close();
23         fos.close();
24         
25         FileInputStream fis=new FileInputStream(new File("f:/test.txt"));
26         ObjectInputStream bis=new ObjectInputStream(fis);
27         SingletonClass clazzThree=(SingletonClass) bis.readObject();
28         System.out.println("clazzThree-hasCode:"+clazzThree.hashCode());
29     }
30 }

  问题:这么轻易就被破解了?那怎么解决呢?

public class SingletonClass implements Serializable{
    
    private static SingletonClass instance;
    
    private SingletonClass(){
        //防止被反射
        if(null!=instance){
            throw new RuntimeException();
        }
    }
    
    public static synchronized SingletonClass getInstance(){
        if(null==instance){
            instance=new SingletonClass();
        }
        return instance;
    }
    
    //当没有定义这方法时,反序列化默认是重新new对象。
    //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}

双重检查锁与内部类

  双重检查锁与内部类的方式:缘由懒汉式、饿汉式要么存在调用效率低或者运行效率低问题。而这两种方式取前两者的优点为自己所用。

 1 /**
 2  * 单例模式-双重检查锁
 3  * @author aaron
 4  */
 5 public class SingletonClass{
 6     private static SingletonClass instance;
 7     
 8     public static  SingletonClass getInstance(){
 9         if(null==instance){
10             synchronized (SingletonClass.class) {
11                 if(instance==null){
12                     instance=new SingletonClass();
13                 }
14             }
15         }
16         return instance;
17     }
18 }

  问题:缘由JVM对于此种方式的同步控制,并不稳定,当高并发的时候,可能会出现问题,并不推荐使用这种方式。理论上来说,它是不存在问题的。

 1 /**
 2  * 单例模式-内部类的方式
 3  * @author aaron
 4  */
 5 public class SingletonClass{
 6     
 7     private static class InnerClass{
 8         public static SingletonClass instance=new SingletonClass(); 
 9     }
10     
11     public static SingletonClass getInstance(){
12         return InnerClass.instance;
13     }
14 }
1 /**
2  * 单例模式-枚举的方式
3  * @author aaron
4  */
5 public enum SingletonClass{
6     INSTANCE
7 }

版权声明

  作者:邱勇Aaron

  出处:http://www.cnblogs.com/qiuyong/

  您的支持是对博主深入思考总结的最大鼓励。

  本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,尊重作者的劳动成果。

优质内容筛选与推荐>>
1、验证:record项元的多少影响修改速度。
2、python--logging库学习_第二波
3、<JavaScript语言精粹>JSON解析器源码阅读
4、Http帮助类(史上最详细帮助类)
5、链表函数(建立,删除重复元素,排序,输出)—— 链表的有序集合


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn