一、介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(这个方法是静态方法)。

二、单例模式的八种方式:

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

    三、饿汉式(静态常量)

    1、步骤如下:

  9. 构造器私有化(防止外部new)

  10. 类的内部创建对象
  11. 向外暴露一个静态的公共方法获取这个类的实例—-getInstance()

    1. public class SingletonTest01 {
    2. // 本类内部创建静态实例
    3. private static final SingletonTest01 SINGLETON = new SingletonTest01();
    4. // 构造器私有
    5. private SingletonTest01() {}
    6. // 对外提供获取该类实例的静态方法
    7. public static SingletonTest01 getInstance() {
    8. return SINGLETON;
    9. }
    10. }

    2、优缺点说明

    image.png

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

    1. public class SingletonTest02 {
    2. // 本类内部创建静态实例
    3. private static final SingletonTest02 SINGLETON;
    4. // 静态代码块
    5. static {
    6. SINGLETON = new SingletonTest02();
    7. }
    8. // 构造器私有
    9. private SingletonTest02() {}
    10. // 对外提供获取该类实例的静态方法
    11. public static SingletonTest02 getInstance() {
    12. return SINGLETON;
    13. }
    14. }

    优缺点说明:
    image.png

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

    1. public class SingletonTest03 {
    2. // 本类内部创建静态实例
    3. private static SingletonTest03 SINGLETON;
    4. // 构造器私有
    5. private SingletonTest03() {}
    6. // 对外提供获取该类实例的静态方法--当使用该方法时,采取实例化该类对象--实现懒加载
    7. public static SingletonTest03 getInstance() {
    8. if (SINGLETON == null) {
    9. SINGLETON = new SingletonTest03();
    10. }
    11. return SINGLETON;
    12. }
    13. }

    优缺点说明:
    image.png

    六、懒汉式(线程安全,同步方法)

    1. public class SingletonTest04 {
    2. // 本类内部创建静态实例
    3. private static SingletonTest04 SINGLETON;
    4. // 构造器私有
    5. private SingletonTest04() {}
    6. // 加锁---synchronized
    7. public static synchronized SingletonTest04 getInstance() {
    8. if (SINGLETON == null) {
    9. SINGLETON = new SingletonTest04();
    10. }
    11. return SINGLETON;
    12. }
    13. }

    优缺点说明:
    image.png

    七、懒汉式(线程不一定安全,同步代码块)

    1. public class SingletonTest04 {
    2. // 本类内部创建静态实例
    3. private static SingletonTest04 SINGLETON;
    4. // 构造器私有
    5. private SingletonTest04() {}
    6. // 加锁---synchronized--同步代码块
    7. public static SingletonTest04 getInstance() {
    8. if (SINGLETON == null) {
    9. synchronized (SingletonTest03.class) {
    10. if (SINGLETON == null) {
    11. SINGLETON = new SingletonTest04();
    12. }
    13. }
    14. }
    15. return SINGLETON;
    16. }
    17. }

    八、双重检查(推荐)

    1. public class SingletonTest05 {
    2. // 本类内部创建静态实例
    3. // volatile--保证可见性--即一个线程改变了值,另一个线程可以立马感知到改变的值
    4. private static volatile SingletonTest05 SINGLETON;
    5. // 构造器私有
    6. private SingletonTest05() {}
    7. public static SingletonTest05 getInstance() {
    8. if (SINGLETON == null) {
    9. synchronized (SingletonTest03.class) {
    10. if (SINGLETON == null) {
    11. SINGLETON = new SingletonTest05();
    12. }
    13. }
    14. }
    15. return SINGLETON;
    16. }
    17. }

    拓展:

    (1)使用反射破解单例模式

    1. public class SingletonTest {
    2. public static void main(String[] args) throws Exception{
    3. SingletonTest05 s1 = SingletonTest05.getInstance();
    4. SingletonTest05 s2 = SingletonTest05.getInstance();
    5. System.out.println(s1);
    6. System.out.println(s2);
    7. /**
    8. * 装载一个类并且对其进行实例化的操作。
    9. * 装载过程中使用到的类加载器是当前类。
    10. */
    11. // 反射破解单例模式
    12. // com.example.demo.thread.SingletonTest05:单例类的全路径类名
    13. //Class<SingletonTest05> clazz = (Class<SingletonTest05>) Class.forName("com.example.demo.thread.SingletonTest05");
    14. Class<SingletonTest05> clazz = SingletonTest05.class;
    15. // getDeclaredConstructor():此方法返回具有指定参数列表构造函数的构造函数对象。使用这个方法私有的构造器也可以拿到
    16. Constructor<SingletonTest05> c = clazz.getDeclaredConstructor(null);
    17. // 设置在使用构造器的时候不执行权限检查
    18. c.setAccessible(true);
    19. SingletonTest05 singletonTest05 = c.newInstance();
    20. System.out.println(singletonTest05);
    21. }
    22. }

    解决方法:

    1. public class SingletonTest05 {
    2. private static volatile SingletonTest05 SINGLETON;
    3. // 构造器私有
    4. private SingletonTest05() {
    5. // 在这里防止反射破解单例模式
    6. // 此时若再使用反射破解单例模式,则直接抛出异常
    7. if (SINGLETON != null) {
    8. throw new RuntimeException("禁止使用反射破解单例模式!");
    9. }
    10. }
    11. public static SingletonTest05 getInstance() {
    12. if (SINGLETON == null) {
    13. synchronized (SingletonTest05.class) {
    14. if (SINGLETON == null) {
    15. SINGLETON = new SingletonTest05();
    16. }
    17. }
    18. }
    19. return SINGLETON;
    20. }
    21. }

    (2)使用序列化破解单例模式

    1. public class SingletonTest1 {
    2. public static void main(String[] args) throws Exception{
    3. SingletonTest05 s1 = SingletonTest05.getInstance();
    4. SingletonTest05 s2 = SingletonTest05.getInstance();
    5. System.out.println(s1);
    6. System.out.println(s2);
    7. // 序列化方式破解单例模式
    8. try (
    9. FileOutputStream fos = new FileOutputStream("d:/a.txt");
    10. ObjectOutputStream oos = new ObjectOutputStream(fos);
    11. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
    12. ) {
    13. oos.writeObject(s1);
    14. SingletonTest05 s3 = (SingletonTest05) ois.readObject();
    15. System.out.println(s3);
    16. } catch (Exception e) {
    17. e.printStackTrace();
    18. }
    19. }
    20. }

    解决方法:

    1. public class SingletonTest05 implements Serializable {
    2. private static volatile SingletonTest05 SINGLETON;
    3. // 构造器私有
    4. private SingletonTest05() {
    5. // 在这里防止反射破解单例模式
    6. // 此时若再使用反射破解单例模式,则直接抛出异常
    7. if (SINGLETON != null) {
    8. throw new RuntimeException("禁止使用反射破解单例模式!");
    9. }
    10. }
    11. // 此方法可防止序列化破解单例模式
    12. // 此时若再使用序列化破解单例模式,则直接返回已经创建好的单例对象
    13. private Object readResolve(){
    14. return SINGLETON;
    15. }
    16. public static SingletonTest05 getInstance() {
    17. if (SINGLETON == null) {
    18. synchronized (SingletonTest05.class) {
    19. if (SINGLETON == null) {
    20. SINGLETON = new SingletonTest05();
    21. }
    22. }
    23. }
    24. return SINGLETON;
    25. }
    26. }

    九、静态内部类(推荐)

    ```java /**

    • 静态内部类有两个特点:
    • 1、当外部类被装载的时候,静态内部类并不会别装载
    • 2、调用getInstance()时静态内部类才会被装载(JVM在装载类的时候是线程安全的) */ public class SingletonTest06 {
  1. // 构造器私有
  2. private SingletonTest06() {}
  3. // 静态内部类
  4. private static class SingletonInstance {
  5. private static final SingletonTest06 SINGLETON = new SingletonTest06();
  6. }
  7. public static SingletonTest06 getInstance() {
  8. return SingletonInstance.SINGLETON;
  9. }

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2925391/1630934874303-01af6066-de1e-4645-bb35-506b00508669.png#height=149&id=JOzVz&margin=%5Bobject%20Object%5D&name=image.png&originHeight=297&originWidth=856&originalType=binary&ratio=1&size=320615&status=done&style=none&width=428)
  2. <a name="ymTjX"></a>
  3. # 十、枚举(推荐)
  4. ```java
  5. public enum SingletonTest07 {
  6. INSTANCE;
  7. public void method() {
  8. System.out.println("这是一个方法");
  9. }
  10. }
  11. // 测试类
  12. class TestDemo {
  13. public static void main(String[] args) {
  14. // 获取单例实例对象
  15. SingletonTest07 instance = SingletonTest07.INSTANCE;
  16. instance.method();
  17. }
  18. }

image.png

优点:

枚举类单例模式可以避免反射和序列化破解单例模式

十一、总结

image.png