单例设计模式最优解(性能、并发、反射、序列化)

2018-12-22  本文已影响8人  古都旧城

所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在。

作用

单例设计模式的关键点

上面的关键点也非必须都要坚持的点,还是需要具体场景具体分析吧。

1、最简单的实现(恶汉)

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

2、性能优化--lazy loaded(懒汉)

为了解决这个问题,我们想到的新的解决方案:

public class SingletonClass { 

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

3、同步(应对多线程问题)

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

4、又是性能

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

5、从源头检查

举例:
创建一个对象 new Object() 看似一句话,但是实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了三件事情:
1、跟实例分配内存;
2、调用类的构造函数,初始化成员字段;
3、将instance 对象指向分配的内存空间(此时instance就不是null了);
1、2、3的顺序可能不一致,所以可能会出错。

6. 解决方案

了这么多,难道单例没有办法在Java中实现吗?其实不然!

说明:Java内存模型分为主内存,和工作内存。主内存是所有的线程所共享的,工作内存是每个线程自己有一个,不是共享的。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(读取、赋值),都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
volatile赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。这同样也导致了volatile也会有一些性能问题,不过影响还是非常小的。

public class SingletonClass { 

  private volatile static SingletonClass instance = null; 

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

  private SingletonClass() { 

  } 

}

这种方法的缺点

其实,还有另外的一种解决方案,并不会受到Java版本的影响:

静态内部类方案(Effiective Java推荐的方式)

public class SingletonClass { 
    
  private static class SingletonClassInstance { 
    private static final SingletonClass instance = new SingletonClass(); 
  } 

  public static SingletonClass getInstance() { 
    return SingletonClassInstance.instance; 
  } 

  private SingletonClass() { 

  } 
    
}

到这里一般就够用了,只不过如果考虑一些奇葩情况当然依然是有一些问题存在的。
1、可以反序列化创建对象。
2、可以反射调用私有构造函数创建对象。

枚举单例设计模式(最安全最简单的方式)

public enum  SingleInstance {
    INSTANCE;
    public void test(){
        System.out.println(INSTANCE.name());
    }
}

调用

public class MainTest {
    public static void main(String[] args) {
        SingleInstance.INSTANCE.test();
    }

}

更多的还有容器类的单例设计管理模式,适合管理很多单例类。

容器的单例设计模式:

/**
 * 单例管理类
 */
public class SingletonManger {
    private static Map<String,Object> objectMap = new HashMap<>();

    /**
     * 私有化管理类,防止创建多个
     */
    private SingletonManger(){
        
    }

    /**
     * 插入单例类
     * @param key
     * @param instance
     */
    public static void registerService(String key,Object instance){
        if(!objectMap.containsKey(key)){
           objectMap.put(key,instance);
        }
    }

    /**
     * 获取单例类
     * @param key
     * @return
     */
    public static Object getSigleInstance(String key){
        return objectMap.get(key);
    }
}
上一篇 下一篇

猜你喜欢

热点阅读