单例模式

定义

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

说明:

这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法)

要点:

  1. 某个类只能有一个实例
  2. 它必须自行创建这个实例
  3. 它必须自行向整个系统提供这个实例

使用场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

单例模式八种实现方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块(和第一个差别不大))
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

1、饿汉式(静态常量)

 1 class Singleton {
 2     // 类内部创建对象
 3     private static final Singleton instance;
 4     static {
 5         instance = new Singleton();
 6     }
 7     // 构造器私有化(防止 new 生成对象)
 8     private Singleton() {
 9     }
10 
11     // 对外暴露一个 static public 方法 (getInstance)
12     public static Singleton getInstance() {
13         return instance;
14     }
15 }
16 
17 public static void main(String[] args) {
18     Singleton s1 = Singleton.getInstance();
19     Singleton s2 = Singleton.getInstance();
20 
21     System.out.println("s1 == s2 ? " + (s1 == s2));
22     System.out.println("s1.hashCode() = " + s1.hashCode());
23     System.out.println("s2.hashCode() = " + s2.hashCode());
24 }
View Code

 基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

优点:避免了多线程的同步问题,没有加锁,效率高;易实现

缺点:类加载时就初始化,没有 lazy loading,可能造成内存浪费

2、饿汉式(静态代码块)

3、懒汉式(线程不安全)

(多线程时,线程一 instance == null 准备 new 一个对象,而这时线程二也可以通过 if 的判断,可能会new 两个对象)

 1 class Singleton {
 2     private static Singleton instance;
 3 
 4     private Singleton() {
 5     }
 6 
 7     // 使用时才创建 instance
 8     public static Singleton getInstance() {
 9         if (instance == null) {
10             instance = new Singleton();
11         }
12         return instance;
13     }
14 }
View Code

这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

优点:lazy loading、易实现

缺点:线程不安全

4、懒汉式(线程安全)

 1 class Singleton {
 2     private static Singleton instance;
 3 
 4     private Singleton() {
 5     }
 6 
 7     // synchronized 同步代码,解决线程安全问题
 8     public static synchronized Singleton getInstance() {
 9         if (instance == null) {
10             instance = new Singleton();
11         }
12         return instance;
13     }
14 }
View Code

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点:lazy loading

缺点:效率低

5、懒汉式(线程安全(其实不安全,效率还低))

 1 class Singleton {
 2     private static Singleton instance;
 3 
 4     private Singleton() {
 5     }
 6 
 7     // 线程不安全
 8     public static Singleton getInstance() {
 9         if (instance == null) {
10             synchronized (Singleton.class) {
11                 instance = new Singleton();
12             }
13         }
14         return instance;
15     }
16 }
View Code

这种写法问题同方式3,不能用这种方式

6、双重检查

volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

详见 https://baike.baidu.com/item/volatile/10606957?fr=aladdin#3  —— 百度百科

 1 class Singleton {
 2     private static volatile Singleton instance;
 3 
 4     private Singleton() {
 5     }
 6     //双重检查,线程安全,lazy loading
 7     public static synchronized 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 }
View Code

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。只有第一次在 instance = null 时才会同步代码

Lazy loading、高效率 、实现较复杂

7、静态内部类

class Singleton {
    private Singleton() {
    }

    // 静态内部类提供实例
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
View Code

这种方式利用了 classloader 机制来保证初始化 instance 时只有一个线程

利用静态内部类延迟加载的机制达到Lazy loading的效果,同时避免了线程安全问题,且效率高

8、枚举

 1 enum Singleton {
 2     INSTANCE;
 3     public void fun() {
 4     }
 5 }
 6 
 7 public static void main(String[] args) {
 8     Singleton s1 = Singleton.INSTANCE;
 9     Singleton s2 = Singleton.INSTANCE;
10 
11     System.out.println("s1 == s2 ? " + (s1 == s2));
12     System.out.println("s1.hashCode() = " + s1.hashCode());
13     System.out.println("s2.hashCode() = " + s2.hashCode());
14 }
View Code

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

源码

JDK中 java.lang.Runtime 就是典型的单例模式的饿汉式 

 总结:

一般情况下,不建议使用懒汉式,建议使用饿汉式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类。如果涉及到反序列化创建对象时,可以尝试使用枚举。如果有其他特殊的需求,可以考虑使用双重检查。

参考资料

https://baike.baidu.com/item/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/5946627?fr=aladdin#1  —— 百度百科

https://www.bilibili.com/video/BV1G4411c7N4?  —— 尚硅谷,韩顺平

https://www.runoob.com/design-pattern/singleton-pattern.html  —— 菜鸟教程

 

posted @ 2020-08-03 15:12  whyha  阅读(171)  评论(0)    收藏  举报