单例模式最好用枚举

Posted saoyou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式最好用枚举相关的知识,希望对你有一定的参考价值。

一说到单例模式,我想你们首先想到的是懒汉式、恶汉式吧!至于登记式(淘汰的模式,可忽略)。

单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

一、懒汉式单例

  先写一个懒汉式的单例模式。

public class Singleton   
    private Singleton()   
    private static Singleton single=null;  
    public static Singleton getInstance()   
         if (single == null)     
             single = new Singleton();  
             
        return single;  
      
 

  Singleton通过将构造方法限定为private避免了其他类通过访问构造器进行实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过静态的getInstance()方法进行访问。

  但是上面的代码是不考虑线程安全的情况下,也就是说,该实例是存在线程安全的。在并发的情况下是可能出现这种情况,就是a线程先进入getInstance()方法在创建实例化的时候,也就是还没创建成功,b线程也进入了getInstance()方法,这个时候a线程实例还没建成功,b线程判断single为空也开始创建实例,导致会出现创建出两个实例来。

  解决方式有三种:

public static synchronized Singleton getInstance()   
    if (single == null)     
        single = new Singleton();  
        
    return single;  
  

加上synchronized关键字,并发的时候也只能一个一个排队进行getInstance()方法访问。

    public static Singleton getInstance()   
        if (singleton == null)     
            synchronized (Singleton.class)     
               if (singleton == null)     
                  singleton = new Singleton();   
                   
                
            
        return singleton;   
     

双重检查锁定,这种方式会优于上面一种方式,在并发量高的情况下,不需要排队进getInstance()方法合理利用系统资源,性能上会优于上面一种。

public class Singleton     
    private static class LazyHolder     
       private static final Singleton INSTANCE = new Singleton();    
        
    private Singleton ()    
    public static final Singleton getInstance()     
       return LazyHolder.INSTANCE;    
        
 

静态内部类实现单例模式,这种方式优于上面两种方式,他即实现了线程安全,又省去了null的判断,性能优于上面两种。

 

二、饿汉式单例

public class Singleton   
    private Singleton()   
    private static final Singleton single = new Singleton();  
    public static Singleton getInstance()   
        return single;  
      

饿汉式是静态加载的时候实,不需要担心线程安全问题。

 

三、枚举单例模式

  以上两种方式是在不考虑放射机制和序列化机制的情况下实现的单例模式,但是如果考虑了放射,则上面的单例就无法做到单例类只能有一个实例这种说法了。事实上,通过Java反射机制是能够实例化构造方法为private的类的。这也就是我们现在需要引入的枚举单例模式。

public enum  EnumSingleton 
    INSTANCE;
    public EnumSingleton getInstance()
        return INSTANCE;
    

   举个例子就能知道上面的单例不是很安全,以双重检索的单例模式为例子,我利用放射,能够创建出新的实例:

public static void main(String[] args) throws Exception 
        Singleton s=Singleton.getInstance();
        Singleton sual=Singleton.getInstance();
        Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s2=constructor.newInstance();
        System.out.println(s+"\n"+sual+"\n"+s2);
        System.out.println("正常情况下,实例化两个实例是否相同:"+(s==sual));
        System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:"+(s==s2));
    

结果为:

[email protected]
[email protected]1641e19d
[email protected]
正常情况下,实例化两个实例是否相同:true
通过反射攻击单例模式情况下,实例化两个实例是否相同:false

由此可见双重检索模式不是最安全的,无法避免反射的攻击。

 

我们检测一下枚举的单例模式

public static void main(String[] args) throws Exception
        EnumSingleton singleton1=EnumSingleton.INSTANCE;
        EnumSingleton singleton2=EnumSingleton.INSTANCE;
        System.out.println("正常情况下,实例化两个实例是否相同:"+(singleton1==singleton2));
        Constructor<EnumSingleton> constructor= null;
        constructor = EnumSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        EnumSingleton singleton3= null;
        singleton3 = constructor.newInstance();
        System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);
        System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:"+(singleton1==singleton3));
    

结果会报Exception in thread "main" java.lang.NoSuchMethodException。出现这个异常的原因是因为EnumSingleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,而且在反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举是不怕发射攻击的。

newInstance方法源码:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    
        if (!override) 
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) 
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            
        
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) 
            ca = acquireConstructorAccessor();
        
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    

 

以上是关于单例模式最好用枚举的主要内容,如果未能解决你的问题,请参考以下文章

单例模式中为什么用枚举更好?

如何对枚举类型实现的单例模式进行mock

为什么用枚举类来实现单例模式越来越流行?

为什么用枚举类来实现单例模式越来越流行?

2020-7-23 枚举实现单例模式和取代判断

单例模式_反射破坏单例模式_枚举类_枚举类实现单例_枚举类解决单例模式破坏