zoukankan      html  css  js  c++  java
  • 单例模式:层层剖析寻找最高效安全的单例

    问题来源

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

      单例模式是指保证在系统中只存在某类唯一对象。运用场景随处可见,例如工具类、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;
    
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> SingletonClass getInstance(){
        </span><span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span>==<span style="color: #000000;">instance){
            instance</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> SingletonClass();
        }
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> 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(</span>"clazzOne-hasCode:"+<span style="color: #000000;">clazzOne.hashCode());
        System.out.println(</span>"clazzTwo-hasCode:"+<span style="color: #000000;">clazzTwo.hashCode());
        
        Class</span>&lt;SingletonClass&gt; clazz=(Class&lt;SingletonClass&gt;)Class.forName("singleton.SingletonClass"<span style="color: #000000;">);
        Constructor</span>&lt;SingletonClass&gt; c=clazz.getConstructor(<span style="color: #0000ff;">null</span><span style="color: #000000;">);
        c.setAccessible(</span><span style="color: #0000ff;">true</span><span style="color: #000000;">);
        SingletonClass clazzThree</span>=<span style="color: #000000;">c.newInstance();
        SingletonClass clazzFour</span>=<span style="color: #000000;">c.newInstance();
        System.out.println(</span>"clazzThree-hasCode:"+<span style="color: #000000;">clazzThree.hashCode());
        System.out.println(</span>"clazzFour-hasCode:"+<span style="color: #000000;">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{
    
    </span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> SingletonClass instance;
    
    </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> SingletonClass(){
        </span><span style="color: #008000;">//</span><span style="color: #008000;">防止被反射</span>
        <span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span>!=<span style="color: #000000;">instance){
            </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> RuntimeException();
        }
    }
    
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">synchronized</span><span style="color: #000000;"> SingletonClass getInstance(){
        </span><span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span>==<span style="color: #000000;">instance){
            instance</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> SingletonClass();
        }
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;
    }
    
    </span><span style="color: #008000;">//</span><span style="color: #008000;">当没有定义这方法时,反序列化默认是重新new对象。
    </span><span style="color: #008000;">//</span><span style="color: #008000;">反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!</span>
    <span style="color: #0000ff;">private</span> Object readResolve() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> ObjectStreamException{
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> 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/

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

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

  • 相关阅读:
    免费的Office批量打印工具 Word、Excel、PDF批量打印
    PHP数据库批量去注释、删字段
    SSL/TLS协议信息泄露漏洞(CVE-2016-2183)【原理扫描】
    CentOS 安装 nginx-1.19.4 与原版本共存
    毕业5年之——上个五年计划复盘20210919
    ubunt 20.04 有道词典命令行工具
    java中针对 try,catch和finally一些总结
    Linux find命令与cp命令连用
    MySQL基本操作笔记
    挖矿病毒排查
  • 原文地址:https://www.cnblogs.com/qiuyong/p/6917801.html
Copyright © 2011-2022 走看看