单例

单例模式 #

Singleton

实现方法 #
  • 初始化时即创建单例
    • 饿汉式
    • 枚举类型
  • 按需,延迟创建单例
    • 懒汉式
      • 基础实现
      • 同步锁
      • 双重检验锁
    • 静态内部类实现
饿汉式(线程安全) #
  • JVM 在类的初始化阶段 (即 在 Class 被加载后、被线程使用前),会执行类的初始化
  • 在执行类的初始化期间,JVM 会去获取一个锁,这个锁可以同步多个线程对同一个类的初始化
class Singleton {
    // 1. 加载该类时,单例就会自动被创建
    private static Singleton ourInstance = new Singleton();

    // 2. 构造函数设置为 私有权限,禁止他人创建实例
    private Singleton() {
    }

    // 3. 通过调用静态方法获得创建的单例
    public static Singleton newInstance() {
        return ourInstance;
    }
}

应用场景

  • 单例对象要求初始化速度快,占用内存小
枚举类(线程安全) #
  • 这是最简洁,最易用的单例实现方式

单元素的枚举类型已经成为实现 Singleton 的最佳方法 - 《Effective Java》

public enum Singleton {
    // 定义 1 个枚举的元素,即为单例类的1个实例
    INSTANCE;

    // 隐藏了 1 个空的、私有的 构造方法
    // private Singleton () {}
}

// 获取单例的方式:
Singleton singleton = Singleton.INSTANCE;
懒汉式 基础实现(线程不安全) #
class Singleton {
    // 1. 类加载时,先不自动创建单例,将单例的引用先赋值为 Null
    private static Singleton ourInstance = null;

    // 2. 构造函数 设置为 私有权限
    // 禁止他人创建实例
    private Singleton() {
    }

    // 3. 需要时才手动调用 newInstance() 创建 单例
    public static Singleton newInstance() {
        // 先判断单例是否为空,以避免重复创建
        if (ourInstance == null) {
            ourInstance = new Singleton();
        }
        return ourInstance;
    }
}
懒汉式 同步锁(线程安全) #
// 写法1
class Singleton {
    // 1. 类加载时,先不自动创建单例
    // 即,将单例的引用先赋值为 Null
    private static Singleton ourInstance = null

    // 2. 构造函数 设置为 私有权限
    // 原因:禁止他人创建实例
    private Singleton() {
    }

    // 3. 加入同步锁
    public static synchronized Singleton getInstance() {
        // 先判断单例是否为空,以避免重复创建
        if (ourInstance == null)
            ourInstance = new Singleton();
        return ourInstance;
    }
}

// 写法2
// 该写法的作用与上述写法作用相同,只是写法有所区别
class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        // 加入同步锁
        synchronized (Singleton.class) {
            if (instance == null)
                instance = new Singleton();
        }
        return instance;
    }
}

缺点

  • 每次访问都要进行线程同步(即 调用 synchronized 锁),造成过多的同步开销(加锁 = 耗时、耗能)
  • 实际上只需在第 1 次调用该方法时才需要同步,一旦单例创建成功后,就没必要进行同步
懒汉式 双重检验锁(线程安全) #
class Singleton {
    private static  Singleton ourInstance  = null

    private Singleton() {
    }

    public static Singleton newInstance() {
        // 加入双重校验锁
        // 校验锁1:第1个if
        if (ourInstance == null) { // ①
            synchronized (Singleton.class) { // ②
                // 校验锁2:第2个 if
                if (ourInstance == null) {
                    ourInstance = new Singleton();
                }
            }
        }
        return ourInstance;
    }
}

// 说明
// 校验锁1:第1个if
// 作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
// 即直接跳到执行 return ourInstance

// 校验锁2:第2个 if
// 作用:防止多次创建单例问题
// 原理
// 1. 线程A调用newInstance(),当运行到②位置时,此时线程B也调用了newInstance()
// 2. 因线程A并没有执行instance = new Singleton();,此时instance仍为空,因此线程B能突破第1层 if
// 判断,运行到①位置等待synchronized中的A线程执行完毕
// 3. 当线程A释放同步锁时,单例已创建,即instance已非空
// 4. 此时线程B 从①开始执行到位置②。此时第2层 if 判断 = 为空(单例已创建),因此也不会创建多余的实例

缺点

  • 实现复杂(多种判断),易出错
静态内部类(线程安全) #
class Singleton {
    // 1. 创建静态内部类
    private static class Singleton2 {
       	// 在静态内部类里创建单例
    	private static  Singleton ourInstance  = new Singleton()
    }

    // 私有构造函数
    private Singleton() {
    }

    // 延迟加载、按需创建
    public static Singleton newInstance() {
        return Singleton2.ourInstance;
    }
}

调用过程说明:

  1. 外部调用类的 newInstance()
  2. 自动调用 Singleton2.ourInstance
    1. 此时单例类 Singleton2 得到初始化
    2. 而该类在装载 & 被初始化时,会初始化它的静态域,从而创建单例;
    3. 由于是静态域,因此只会 JVM 只会加载 1 遍,Java 虚拟机保证了线程安全性
  3. 最终只创建 1 个单例

缺点

  • 单例类的职责过重,里面的代码可能会过于复杂,在一定程度上违背了 “单一职责原则”
  • 如果实例化的对象长时间不被利用,会被系统认为是垃圾而被回收,这将导致对象状态的丢失

代码示例 #


本文访问量

本站总访问量

本站总访客数