单例模式的写法(看完这个就够了)

2,379 阅读8分钟

不管以那种形式实现单例模式,核心原理就是将构造函数私有化,并且通过静态方法获取一个唯一的实例。在这个获取过程中必须保证线程安全、防止序列化导致重新生成实例对象等问题。

1.懒汉式

添加synchronized可以在多线程情况下保证单例对象的唯一性

优点:单例只有在使用的时候才会进行实例化,在一定程度上节约了资源。

缺点:第一次加载需要实例化,反应稍慢,每次调用时都同步,造成不必要的开销。

  1. public class Singleton {  
  2.     private static Singleton instance;  
  3.   
  4.     private Singleton() {  
  5.     }  
  6.   
  7.     public static synchronized Singleton getInstance() {  
  8.         if (instance == null) {  
  9.             instance = new Singleton();  
  10.         }  
  11.         return instance;  
  12.     }  
  13. }  
public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

2.饿汉式

在类装载时就进行了实例化

优点:没有加锁,线程安全,执行效率高

缺点:类加载时就初始化,浪费资源

  1. public class Singleton {  
  2.     private static Singleton instance = new Singleton();  
  3.   
  4.     private Singleton() {  
  5.     }  
  6.   
  7.     public static Singleton getInstance() {  
  8.         return instance;  
  9.     }  
  10. }  
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}
  1. public class Singleton {  
  2.     private static Singleton instance = null;  
  3.   
  4.     static {  
  5.         instance = new Singleton();  
  6.     }  
  7.   
  8.     private Singleton() {  
  9.     }  
  10.   
  11.     public static Singleton getInstance() {  
  12.         return instance;  
  13.     }  
  14. }  
public class Singleton {
    private static Singleton instance = null;

    static {
        instance = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

3.双重检验锁(DCL)

对懒汉式进一步的完善,不仅可以避免每次都进行同步造成不必要的开销,也可以在需要的时候在进行实例化,节省资源。

  1. public class Singleton {  
  2.     private volatile static Singleton instance = null;  
  3.   
  4.     private Singleton() {  
  5.     }  
  6.   
  7.     public static Singleton getInstance() {  
  8.         if (instance == null) {  
  9.             synchronized (Singleton.class) {  
  10.                 if (instance == null) {  
  11.                     instance = new Singleton();  
  12.                 }  
  13.             }  
  14.         }  
  15.         return instance;  
  16.     }  
  17. }  
public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton() {
    }

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

上面添加了volatile关键字,如果没有volatile关键字,在执行instance = new Singleton()时可能会出现问题,伪代码如下:
  1. inst = allocat();   // 第一步:分配内存  
  2. constructor(inst);  // 第二步:执行构造函数  
  3. instance = inst;    // 第三步:赋值,将instance对象指向分配的内存空间(此时instance就不是null了)  
    inst = allocat();   // 第一步:分配内存
    constructor(inst);  // 第二步:执行构造函数
    instance = inst;    // 第三步:赋值,将instance对象指向分配的内存空间(此时instance就不是null了)
这个地方涉及到了java内存模型。

由于Java编译器允许处理器乱序执行,所以第二步和第三步的顺序无法保证。如果第三步先执行完毕、第二步未执行时,有另外的线程调用了instance,由于已经赋值,将判断不为null,拿去直接使用,但其实构造函数还未执行,成员变量等字段都未初始化,直接使用,就会报错。这就是DCL失效问题,而且很难复现。

对volatile变量的写操作,不允许和它之前的读写操作打乱顺序;对volatile变量的读操作,不允许和它之后的读写乱序。

当一个线程要使用共享内存中的volatile变量时,它会直接从主内存中读取,而不是使用自己本地内存中的副本。当一个线程对一个volatile变量进行写时,它会将这个共享变量值刷新到共享内存中。

volatile的使用,或多或少会影响性能,但是对于程序的稳定性来说,这点牺牲不算什么。上面的代码还可以优化:

  1. public class Singleton {  
  2.     private volatile static Singleton instance = null;  
  3.   
  4.     public static Singleton getInstance() {  
  5.         Singleton inst = instance; // 创建临时变量  
  6.         if (inst == null) {  
  7.             synchronized (Singleton.class) {  
  8.                 inst = instance;  
  9.                 if (inst == null) {  
  10.                     inst = new Singleton();  
  11.                 }  
  12.             }  
  13.         }  
  14.         return inst; // 返回临时变量  
  15.     }  
  16.   
  17.     private Singleton() {}  
  18. }  
public class Singleton {
    private volatile static Singleton instance = null;

    public static Singleton getInstance() {
        Singleton inst = instance; // 创建临时变量
        if (inst == null) {
            synchronized (Singleton.class) {
                inst = instance;
                if (inst == null) {
                    inst = new Singleton();
                }
            }
        }
        return inst; // 返回临时变量
    }

    private Singleton() {}
}

我们添加了一个临时变量,这样除了第一次初始化之外,之后的访问,都会减少对instance的访问,从未在一定程度上提高性能。

4.枚举

写法简单,线程安全

  1. public enum Singleton {  
  2.     INSTANCE;  
  3. }  
public enum Singleton {
    INSTANCE;
}

写法简单,是枚举的最大特点,最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下他都是一个单例。
我们使用单例模式就是为了某个类的实例是唯一的。但如果这个类是可以序列化的时,比如实现了Serializable接口等情况下,通过序列化可将一个单例的实例对象写到磁盘,然后在都会来,从而有效的获得一个实例。即使函数的构造方法是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用了该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve(),这个方法可以让开发人员控制对象的反序列化。为了保证反序列化的过程中仍然保持单例的特性,可以在单例中添加一个readResolve()方法

  1. private Object readResolve() throws ObjectStreamException {  
  2.     return instance;  
  3. }  
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }

在反序列化从I/O流中读取读取对象时,readResolve()方法会被调用,实际上就是用readResolve()中返回的对象直接替换掉在反序列化中创建的对象。

参考连接:developer.51cto.com/art/201202/…

5.静态内部类单例

线程安全,能保证唯一性,在需要的时候再进行实例化

  1. public class Singleton {  
  2.     private Singleton() {  
  3.     }  
  4.   
  5.     public static Singleton getInstance() {  
  6.         return SingletonInstance.instance;  
  7.     }  
  8.   
  9.     private static class SingletonInstance {  
  10.         private static final Singleton instance = new Singleton();  
  11.     }  
  12. }  
public class Singleton {
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonInstance.instance;
    }

    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }
}

6.容器实现单例模式

  1. public class Singleton {  
  2.     private static Map<String, Object> instanceMap =  new HashMap<String, Object>();  
  3.   
  4.     private Singleton() {  
  5.     }  
  6.   
  7.     public static void addInstance(String key, Object instance) {  
  8.         if (!instanceMap.containsKey(key)) {  
  9.             instanceMap.put(key, instance);  
  10.         }  
  11.     }  
  12.   
  13.     public static Object getInstance(String key) {  
  14.         return instanceMap.get(key);  
  15.     }  
  16. }  
public class Singleton {
    private static Map<String, Object> instanceMap = new HashMap<String, Object>();

    private Singleton() {
    }

    public static void addInstance(String key, Object instance) {
        if (!instanceMap.containsKey(key)) {
            instanceMap.put(key, instance);
        }
    }

    public static Object getInstance(String key) {
        return instanceMap.get(key);
    }
}



参考连接:

segmentfault.com/a/119000000…

www.infoq.com/cn/articles…

www.race604.com/java-double…

mp.weixin.qq.com/s?__biz=MzA…