单例模式Singleton

单例模式是一种创建型设计模式,确保一个类只有一个实例并提供全局访问点。文章介绍了Java中多种单例模式的实现方式,如饿汉式、懒汉式、双检锁(DCL)、静态内部类和枚举,以及如何通过反射和反序列化破解单例。推荐使用枚举方式实现单例,因为它既线程安全又防止反射和序列化攻击。
摘要由CSDN通过智能技术生成

单例模式Singleton

https://refactoringguru.cn/design-patterns/singleton

https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples

https://blog.csdn.net/hzygcs/article/details/88627399

https://blog.csdn.net/qq_40794973/article/details/104165984

https://www.cnblogs.com/chiclee/p/9097772.html

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
客户端甚至可能没有意识到它们一直都在使用同一个对象。

1.问题

单例模式同时解决了两个问题, 所以违反了单一职责原则

  1. 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
    在这里插入图片描述

  2. 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例

2.解决方案

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

3.真实世界类比

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

4.单例模式结构

在这里插入图片描述

5.单例模式的写法与破解

1.饿汉式(静态常量)[可用]
/**
 * 饿汉式(静态常量)(可用)
 */
public class Singleton {
    private final static Singleton instance = new Singleton();
    // 构造函数私有化
    private Singleton() {
        //....
    }
    public static Singleton getInstance() {
        return instance;
    }
}
  • 线程安全性: 实例在类加载的时候即被实例化,因此线程安全。
  • 是否懒加载: 没有延迟加载,如果长时间没用到这个实例,则会造成内存的浪费。
  • 性能: 性能比较好
2.饿汉式(静态代码块)[可用]
/**
 * 饿汉式(静态代码块)(可用)
 */
public class Singleton {
    private final static Singleton instance;
    static {
        // 构造对象可能还会从配置文件读取配置
        Properties prop = new Properties();
        try {
            prop.load(Singleton.class.getClassLoader().getResourceAsStream("single.properties"));
        } catch (IOException e) {
            throw new RuntimeException("加载配置文件single.properties失败");
        }
        String name = prop.getProperty("jdbc.name");
        // 构造对象
        instance = new Singleton();
    }
    // 私有构造函数
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}
### single.properties
jdbc.name=root
jdbc.password=123456
/**
 * 写法不当容易产生NPE
 */
public class Singleton {
    static {
        instance = new Singleton();
    }
    private static Singleton instance = null;//应该写在静态代码块前面
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
    }
}
  • 线程安全性: 实例在类加载的时候即被实例化,因此线程安全。
  • 是否懒加载: 没有延迟加载,如果长时间没用到这个实例,则会造成内存的浪费。
  • 性能: 性能比较好
3.懒汉式(线程不安全)[不可用]
/**
 * 懒汉式(线程不安全)
 */
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
//验证线程安全
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        //...
    }

    public static Singleton getInstance() {
        if (instance == null) {
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Singleton> task = Singleton::getInstance;
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Singleton> f1 = executor.submit(task);
        Future<Singleton> f2 = executor.submit(task);

        Singleton s1 = f1.get();
        Singleton s2 = f2.get();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
        executor.shutdown();
    }
}
  • 线程安全性:单线程下没有问题,多线程下有可能会生成多个实例,因此线程不安全。
    • 问题出现在if (null == instance)这一步,多线程环境中可能会有多个线程同时拿到instance的值为null。
  • 是否懒加载: 懒加载
  • 性能: 性能比较好
4.懒汉式(线程安全,同步方法)[不推荐]
/**
 * 懒汉式(线程安全)(不推荐)
 */
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        //...
    }
    //多个线程无法并行执行,不能及时的响应
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 线程安全性: 线程安全
  • 是否懒加载: 懒加载
  • 性能: synchronized修饰方法,使得多线程执行退化为串行执行,性能差
5.懒汉式(线程不安全,同步代码块)[不可用]
/**
 * 懒汉式(线程不安全)(不推荐)
 */
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        //并没有解决线程安全问题
        if (instance == null) {
            //这里虽然是同步的,但是还是会多次运行
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}
  • 线程安全性:单线程下没有问题,多线程下有可能会生成多个实例,因此线程不安全。
    • 问题同样出现在if (null == instance)这一步
  • 是否懒加载: 懒加载
  • 性能: 性能一般
6.DCL+Volatile [可用]

引入双重锁,即Double-Check-Locking。同时引入volatile关键字修饰实例对象,这是为了避免因JVM指令重排序可能导致的空指针异常。因为当线程执行到第一个if (null == instance)时,代码可能读取到instance不为null,但此时instance引用的对象可能还没有完成初始化。

/**
 * 双重检查(推荐面试使用)
 */
public class Singleton {
    //注意volatile
    //1.可见性
    //2.防止重排序
    private volatile static Singleton instance;
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                //虽然可能进来多个,但是我再做一次检查即可避免多次创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为什么要double-check

因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。
但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈。

为什么要用volatile

就是要你懂Java中volatile关键字实现原理:http://www.cnblogs.com/xrq730/p/7048693.html

  • 线程安全性: 线程安全性
  • 是否懒加载: 懒加载
  • 性能: 性能较好
7.Holder模式(静态内部类) [推荐]

声明类时,成员变量中不声明实例变量,而是放到静态内部类中。这种方式和懒汉式有些相似,它们都采用了类装载的机制来保证初始化实例时只有一个线程,不同的是,Holder单例模式是将实例的初始化放到了静态类中去实现,从而实现了懒加载。

/**
 * 静态内部类方式,可用
 */
public class Singleton {
    private Singleton() {
        //...
    }
    // JVM类加载的性质,保证了即便多个线程同时访问这个SingletonInstance,也不会创建多个实例
    // 在内部类被加载和初始化时,才创建INSTANCE实例对象
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
  • 线程安全性: 线程安全性
  • 是否懒加载: 懒加载
  • 性能: 性能好
8.序列化

有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便我们可以将其状态存储在文件系统中,并在以后的某个时间点检索它。

public class Singleton implements Serializable {
    private static final long serialVersionUID = -1291257263696872928L;
    private Singleton() {
    }
    static class SingletonHelper {
        private static final Singleton instance = new Singleton();
    }
    static Singleton getInstance() {
        return SingletonHelper.instance;
    }
}

序列化单例类的问题在于,每当反序列化它时,它将创建该类的新实例。

package org.example.jvm01;

import java.io.*;

public class SingletonSerializedTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Singleton instanceOne = Singleton.getInstance();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
        Singleton instanceTwo = (Singleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
        System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
    }
}

结果

instanceOne hashCode = 325040804
instanceTwo hashCode = 764977973
9.枚举 [最佳]

枚举成员默认都被final、public、static修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。

为什么要用枚举实现单例模式(避免反射、序列化问题) https://www.cnblogs.com/chiclee/p/9097772.html

写法一

/**
 * 枚举单例(推荐生产实践中使用)
 */
public enum Singleton {
    INSTANCE;
    /**
     * 类中的方法
     */
    public Connection jdbcConnection() {
        String user = "root";
        String password = "123456";
        String url = "jdbc:mysql://......";
        String className = "com.mysql.cj.jdbc.Driver";
        try {
            Class.forName(className);
            return DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Connection conn = Singleton.INSTANCE.jdbcConnection();
    }
}

写法二

public class SingletonExample {
    // 私有构造函数
    private SingletonExample() {
    }
    public static SingletonExample getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
    private enum Singleton {
        INSTANCE;
        private SingletonExample singleton;
        // JVM保证这个方法绝对只调用一次
        Singleton() {
            singleton = new SingletonExample();
        }
        public SingletonExample getInstance() {
            return singleton;
        }
    }
}
  • 线程安全性: 线程安全性
  • 是否懒加载: 懒加载
  • 性能: 性能好
10.破解单例模式

反射可用于销毁所有上述单例实现方法。

10.1.反射
10.1.1.反射非枚举单例
public class ReflectionSingletonTest {
    public static void main(String[] args) {
        Singleton instanceOne = Singleton.getInstance();
        Singleton instanceTwo = null;
        try {
            Constructor<?>[] declaredConstructors = Singleton.class.getDeclaredConstructors();
            for (Constructor<?> declaredConstructor : declaredConstructors) {
                declaredConstructor.setAccessible(true);
                instanceTwo = (Singleton)declaredConstructor.newInstance();
                break;
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("instanceOne.hashCode():" + instanceOne.hashCode());
        System.out.println("instanceTwo.hashCode():" + instanceTwo.hashCode());
    }
}
10.1.2.反射枚举单例
public enum Singleton implements Serializable {
    INSTANCE;
    public static void doSomething(){
        //do something
    }
}
public class ReflectionSingletonTest {
    public static void main(String[] args) {
        Singleton instanceOne = Singleton.INSTANCE;
        Singleton instanceTwo = null;
        try {
            Constructor<?>[] declaredConstructors = Singleton.class.getDeclaredConstructors();
            for (Constructor<?> declaredConstructor : declaredConstructors) {
                declaredConstructor.setAccessible(true);
                instanceTwo = (Singleton)declaredConstructor.newInstance();
                break;
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("instanceOne.hashCode():" + instanceOne.hashCode());
        System.out.println("instanceTwo.hashCode():" + instanceTwo.hashCode());
    }
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
破解失败
11.反序列化
11.1.1.反序列化非枚举单例
package org.example.jvm01;

import java.io.*;

public class SingletonSerializedTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Singleton instanceOne = Singleton.getInstance();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
        Singleton instanceTwo = (Singleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
        System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
    }
}
11.1.2.反序列化枚举单例
public enum Singleton implements Serializable {
    INSTANCE;
    public static void doSomething(){
        //do something
    }
}
package org.example.jvm01;

import java.io.*;

public class SingletonSerializedTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Singleton instanceOne = Singleton.INSTANCE;
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
        Singleton instanceTwo = (Singleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
        System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
    }
}
instanceOne hashCode = 2133927002
instanceTwo hashCode = 2133927002

破解失败
instanceOne hashCode = 2133927002
instanceTwo hashCode = 2133927002

破解失败
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值