单例的实现方法总结

2019-09-28  本文已影响0人  白花蛇草可乐

单例的实现方法总结

以下的内容不涉及基础,比如什么是单例?JVM类加载顺序?等等。

仅仅是对所有单例的实现方法进行汇总。

一、最经典的饿汉模式实现方式

public class Singleton1 {
    private final static Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){
    }
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

另外一个变种的实现方法,是将静态成员改为静态代码块

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

不管怎么写,本质上利用的都是“类的初始化过程(包含静态成员赋值,以及静态代码块的执行),只在类被加载到内存时执行一次”这一特性。

  1. 优点

  1. 缺点

二、懒汉模式实现方法

public class Singleton2 {
    private static Singleton2 instance;
    private Singleton2 (){
    }
    public static synchronized Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}
  1. 优点

  1. 缺点

三、双重检查方法

双重检查其实就是对于懒汉模式的一种性能改进,减小了synchronized关键字锁定的代码块范围。
第二重检查的作用是:防止有别的线程,在第一重检查和拿锁之间创建了单例实例。

public class Singleton3 {
    private volatile static Singleton3 instance;
    private Singleton3 (){
    }
    public static Singleton3 getSingleton() {
        if (instance == null) {
            synchronized (Singleton3.class) {
                if (instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

四、静态内部类方法

这种方法,也是利用了类加载的特性,在getInstance()方法调用静态内部类的静态成员变量时,静态内部类SingletonHolder才会被初始化,创建单例实例。
(复习:使用 Class.staticMember 方式引用类的静态成员变量,属于对类进行主动引用,在这种情况下会触发类加载的初始化过程)

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

优点

五、枚举类

这是一种最简洁但是最难理解的单例实现方法。但是《Effective Java》评价这是实现单例的最佳方法(参看该书第3条)

public enum Singleton5 {
    /**
     * 枚举实现单例
     */
    INSTANCE;
    public void businessMethod() {
    }
}  

其调用方法如下:

Singleton5.INSTANCE.businessMethod()

下面解释枚举类为什么能实现单例。

  1. 首先对于Singleton5编译好的class进行反编译

因为enum只是一个关键字,不是超类或者其他能看到源码的东西。因此利用反编译的手段来确认内部实现(可以使用jad等工具)。

package singleton;


public final class Singleton5 extends Enum
{

    public static Singleton5[] values()
    {
        return (Singleton5[])$VALUES.clone();
    }

    public static Singleton5 valueOf(String name)
    {
        return (Singleton5)Enum.valueOf(singleton/Singleton5, name);
    }

    private Singleton5(String s, int i)
    {
        super(s, i);
    }

    public void businessMethod()
    {
    }

    public static final Singleton5 INSTANCE;
    private static final Singleton5 $VALUES[];

    static 
    {
        INSTANCE = new Singleton5("INSTANCE", 0);
        $VALUES = (new Singleton5[] {
            INSTANCE
        });
    }
}

  1. 枚举如何保证线程安全

可以看到,使用enum关键字的话,实际会生成一个继承了Enum,并且final的类。

public final class Singleton5 extends Enum

注意下面的这一段:

    public static final Singleton5 INSTANCE;
    private static final Singleton5 $VALUES[];

    static 
    {
        INSTANCE = new Singleton5("INSTANCE", 0);
        $VALUES = (new Singleton5[] {
            INSTANCE
        });
    }
  1. 解决反序列化问题

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }
    T[] getEnumConstantsShared() {
        if (enumConstants == null) {
            if (!isEnum()) return null;
            try {
                final Method values = getMethod("values");
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                values.setAccessible(true);
                                return null;
                            }
                        });
                @SuppressWarnings("unchecked")
                T[] temporaryConstants = (T[])values.invoke(null);
                enumConstants = temporaryConstants;
            }
            // These can happen when users concoct enum-like classes
            // that don't comply with the enum spec.
            catch (InvocationTargetException | NoSuchMethodException |
                   IllegalAccessException ex) { return null; }
        }
        return enumConstants;
    }

六、应对多个类加载器

前面的所有方法都有一个共通的问题:被多个类加载器加载。

这问题不算是钻牛角尖,一些热启动机制的框架,就是利用多个类加载器实现的,这时候确实有可能造成单例变成多例。

在网上找到了一段代码来解决这个问题,就是增加下面这个私有静态类。

原理是在被调用getClass方法时,直接利用自身原来的类加载器进行类加载,确保自始至终一直是同一个类加载器在加载单例类。

private static Class getClass(String classname) throws ClassNotFoundException {     
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
      
      if(classLoader == null)     
         classLoader = Singleton.class.getClassLoader();     
      
      return (classLoader.loadClass(classname));     
   }     
}  
上一篇下一篇

猜你喜欢

热点阅读