美文网首页
单例模式

单例模式

作者: 眼前or远方 | 来源:发表于2016-10-22 17:53 被阅读0次

    平时工作开发中,对面做着个4年多的java开发。每次看完我的代码,都摇摇头,你这个设计台low B了,代码都拢在了一块儿。再这样的激发下,当然也感受到了代码架构的重要性,所以自己就开始了设计模式的学习。因为自己是个菜鸟,所以我会分析的很详细,把只要有一点点疑惑的地方都搞懂,然后分享出来,希望在我进步的同时也能帮到大家把。那时候也不早了,开始入正题吧。

    选择单例模式是因为在我的影响中单例模式好像是最简单的一种模式,因为其他模式不敢说有没有用,但单例模式在开发中肯定是有用过的。来看看平时用的单例是哪种。

    平时使用比较多的单例模式

    懒汉模式(线程不安全)

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

    这个也是我以前经常写的方式,但是这个写法最大的问题就是并发的情况下,线程不安全,比如有多个线程同时去调用getInsance()这样就可能初始化多个singleton对象。那我们来对这个方法进行改进,现在不是多线程的问题嘛,那就用synchronized 修饰呗,好改完后。

    懒汉模式(线程安全)

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

    这样就解决了多线程下的问题,但是带来的问题是什么呢?因为我们的锁其实就是给第一次访问时用的,访问过一次以后 instance就有值了,这个时候就算是多线程 if (null == instance)这个判断都不能通过,所以第一用过后 这个synchronized就没有意义了,而我们知道synchronized影响效率,所以这个不是很好的方式。继续改进:

    双重锁定(double check)

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

    这个时候我们来分析下流程,当多线程访问的时候,同时进到getInstance()方法中,然后判断是不是null 不是,ok继续走,有一个线程进入同步方法里面,另外的在外面等着,进去后的线程发现instance是null new出来。好,第二个线程进入,已经不等于null了,直接返回了。 第一次没有问题,然后看第二次,第二次访问时,直接在第一个判断null==insance,因为instance已经不是null了,所以直接返回。这样没进入同步方法,这样看上去我们已经解决了线程安全和效率这两大问题,看来已经完美。 但是这个地方还是有一个地方有隐患的, instance = new Singleton();这个new方法,他并非一个原子操作(这个暂时理解为 按顺序执行的操作),new的时候再虚拟机里面会有三个步骤
    1、给instance分配一个内存地址
    2、初始化Singleton对象。以及构造函数的一些参数
    3、让singleton对象指向instance的地址(这个时候instance就不为null了)
    那刚才说他不是原子操作意思就是说,他并不一定完全是这个顺序,那他也可能步骤132那当他先执行了3的时候,其实instance还是空。那如果这个时候刚好多线程又去new了,那这又线程不安全了。好,解决这个问题,能不能让他保证顺序的执行,在他没执行完这个步骤时,其他线程不能进来,violate这个修饰符,可以做这个事情,那代码变成这样了:

    public class Singleton { 
    private violate static Singleton instance; 
    private Singleton() {}
     public  static Singleton getInstance() {
    
     if (null == instance) { 
    synchronized(Singleton.this){
    if (null == instance){
       instance = new Singleton();
      } 
    }
          } 
    return instance; 
      }
    }
    

    当然这个violate的效果相当于,你必须把步骤走完,其他线程才能执行,但是这个violate效果只能java5以后才可以。
    这样我们从平常的代码出发,已经写出了一个近似完美的单例模式了,但是貌似代码有点多,有点复杂。 我们再来看看单例还有哪些更简单,更安全的方式。

    单例的静态内部类(eagerly饿汉模式)

    上代码:

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

    为啥叫恶汉呢,因为他在类初始化的时候就new了,而且是静态唯一的,所以也就不存在多线程的问题。当然也有问题,问题是如果我不调用getinstance方法 ,是不是instance的内存就浪费了。而且主要是我如果想传递一些参数,你自己一开始就加载好了,我就传过来了。后面这个问题挺大。

    单例的内部静态类模式

    相对于饿汉来的那么直接,懒汉相对就含蓄点。来,看代码:

    public class Singleton { 
    private static class SingletonHolder { 
    private static final Singleton INSTANCE = new Singleton(); 
    } 
    private Singleton (){} 
    public static final Singleton getInstance() { 
    return SingletonHolder.INSTANCE; 
    }
     }
    

    静态内部类,木有饿汉那么饥渴,调用方法的时候才去new,没有安全性问题,只加载一次。没有性能问题,没有同步啥的,关键代码也挺少(没说最少,下面还有个最少的)。挺完美,还是蛮推荐的。

    单例的枚举模式(enum)

    public enum Singleton{ INSTANCE; }
    

    我靠,这也太简单了吧,关键是他还具有单例所需要的所有优点,安全,性能。简单解释一下,其实枚举在编译完以后生成的是一个类继承enum的
    上面的那个枚举类,编译后是这样的

    public class Singleton extends Enum{
    public static final Singleton  INSTANCE;
    }
    

    枚举默认就是线程安全的,并且他是静态final的 所以也能保证唯一,其实这种写法还有好处就是可以直接序列化,并且也不用担心被反射。

    单例的问题

    上面介绍了实现单例模式的方法,但是单例模式会不会被其他的机制或方法破坏掉,或者因为单例而不能实现某些功能。这个当然是有的,不然我也不写了,比如反射能破坏单例,比如序列化怎么办?下面一个个介绍,思路还是问题---解决方案。

    反射带来的问题及解决方案

    反射带来的问题其实很好理解,通过反射可以拿到私有的属性,方法等,这样他就可以重新new出一个对象,这个时候破坏了我们的单例。解决方案是通过抛出异常来解决,拿双重锁定举例。

    public class Singleton { 
    private static Singleton instance; 
    
    private static boolean flag=false;
    
    private Singleton() { 
    synchonized(Singleton.class){ 
      if(flag==false){
      flag=true;
    }else{
             throw new RuntimeException("单例模式被侵犯!");  
    }
    }
    
    }
     public  static Singleton getInstance() {
    
     if (null == instance) { 
    synchronized(Singleton.this){
    if (null == instance){
       instance = new Singleton();
      } 
    }
          } 
    return instance; 
      }
    }
    

    在构造函数里面抛出异常,这样如果通过反射如果重新new对象的话,就会报错了。

    单例的序列化

    单例中实现序列化的方式是实现seriliziable接口,然后还需要readResolve方法。

    public class Singleton implement Serializable { 
    private static Singleton instance; 
    
    private static boolean flag=false;
    
    private Singleton() { 
    synchonized(Singleton.class){ 
      if(flag==false){
      flag=true;
    }else{
             throw new RuntimeException("单例模式被侵犯!");  
    }
    }
    
    }
     public  static Singleton getInstance() {
    
     if (null == instance) { 
    synchronized(Singleton.this){
    if (null == instance){
       instance = new Singleton();
      } 
    }
          } 
    return instance; 
      }
    
     private Object readResolve() { return singleton; }//实现序列化需要的方法
    }
    

    当然为什么需要这么写,大家可以参考这个。
    [单例的序列化] http://www.hollischuang.com/archives/1144

    结语

    对设计模式的简单总结,其实还有一些疑点我也没打开,比如有人说多classloader对于单例模式的影响,这个我也没去细看,如果有清楚的可以赐教。第一次的设计模式学习分享,参考了很多大神的文章,在此就不一一列出了,对这些默默分享的人我只能说感谢,并且自己也致力于成为这样的人。当然自己现在文章如果有错漏的地方,请不吝指出,感谢!最后给一个github上所有设计模式的例子的链接,如果有人想学习可以去参考下。
    [设计模式]https://github.com/iluwatar/java-design-patterns

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/gythuttx.html