• 单例设计模式


    单例模式

    概念:

            在应用程序中保证有且只有一个实例

    1.懒汉式

    1. public class Single implements Serializable {
    2. private static final long serialVersionUID = 1L;
    3. private static Single single;
    4. private static volatile boolean flag = false;
    5. private Single() {
    6. /** 防止反射多线程调用 */
    7. synchronized(Single.class) {
    8. /**
    9. * 防止反射破坏单例模式
    10. * 若flag为true,表示已经通过构造器创建过实例,则抛出异常,为false则正常创建实例。
    11. */
    12. if (flag) {
    13. throw new RuntimeException("不可重复创建Single实例");
    14. }
    15. flag = true;
    16. }
    17. }
    18. public static Single getInstance(){
    19. if (this.single == null) {
    20. // 使用同步锁,防止多线程下创建多个对象
    21. synchronized (Single.class) {
    22. // 双重校验
    23. if (this.single == null){
    24. single = new Single();
    25. }
    26. }
    27. }
    28. return single;
    29. }
    30. /**
    31. * 防止反序列化破坏单例模式(为什么要创建readResolve方法,具体看源码)(如果类未实现Serializable接口,该方法可以忽略)
    32. */
    33. public Object readResolve() {
    34. return single;
    35. }
    36. }

    总结:

            1.懒汉式是在第一次使用实例的时候去创建实例,创建完后会一直存在,直到系统停止;

            2.显性创建实例,多线程下线程不安全(多线程下使用synchronized和双重锁来解决线程安全)。

    2.饿汉式(静态变量)

    1. public class Single implements Serializable {
    2. private static final long serialVersionUID = 1L;
    3. /** 在加载Single类的时候创建instance变量(系统启动时会加载类) */
    4. private static final Single INSTANCE = new Single();
    5. private static volatile boolean flag = false;
    6. /** 私有化构造器 */
    7. private Single(){
    8. /** 防止反射多线程调用 */
    9. synchronized(Single.class) {
    10. /**
    11. * 防止反射破坏单例模式
    12. * 若flag为true,表示已经通过构造器创建过实例,则抛出异常,为false则正常创建实例。
    13. */
    14. if (flag) {
    15. throw new RuntimeException("不可重复创建Single实例");
    16. }
    17. flag = true;
    18. }
    19. }
    20. /** 对外提供获取实例的方法 */
    21. public static Single getInstance(){
    22. return INSTANCE;
    23. }
    24. /**
    25. * 防止反序列化破坏单例模式
    26. */
    27. public Object readResolve() {
    28. return INSTANCE;
    29. }
    30. }

    3.饿汉式(静态代码块)

    1. public class Single implements Serializable {
    2. private static final long serialVersionUID = 1L;
    3. private static final Single INSTANCE;
    4. private static volatile boolean flag = false;
    5. static {
    6. INSTANCE = new Single();
    7. }
    8. private Single() {
    9. /** 防止反射多线程调用 */
    10. synchronized(Single.class) {
    11. /**
    12. * 防止反射破坏单例模式
    13. * 若flag为true,表示已经通过构造器创建过实例,则抛出异常,为false则正常创建实例。
    14. */
    15. if (flag) {
    16. throw new RuntimeException("不可重复创建Single实例");
    17. }
    18. flag = true;
    19. }
    20. }
    21. /** 对外提供获取实例的方法 */
    22. public static Single getInstance(){
    23. return INSTANCE;
    24. }
    25. /**
    26. * 防止反序列化破坏单例模式(为什么要创建readResolve方法,具体看源码)(如果类未实现Serializable接口,该方法可以忽略)
    27. */
    28. public Object readResolve() {
    29. return INSTANCE;
    30. }
    31. }

    4.饿汉式(枚举)(枚举本身是线程安全的)

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

    总结:

            1.饿汉式是在类加载初始化时创建唯一实例,该实例在整个系统运行中会一直存在,不会被垃圾回收,直到系统停止;

            2.隐性创建实例,使用方便,但存在内存浪费的问题(生命周期与系统的生命周期一致);

            3.线程安全。

    破坏单例模式的方式(以枚举创建的单例模式除外)

            1.反序列化(若单例模式未实现Serializable接口可忽略 ,只有实现了Serializable接口才会出现反序列化破坏单例模式的情况)

    1. public class test {
    2. public static void main (String[] args) {
    3. writeObjectToFile();
    4. readObjectFromFile()
    5. }
    6. // 向文件中读数据
    7. public static void readObjectFromFile() throws Exception {
    8. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Lenovo\\Desktop\\a.txt"));
    9. Single s1 = (Single) ois.readObjecct();
    10. Single s2 = (Single) ois.readObjecct();
    11. System.out.println(s1 == s2);
    12. ois.close();
    13. }
    14. // 向文件中写数据
    15. public static void writeObjectToFile() throws Exception {
    16. Single single = Single.getInstance();
    17. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Lenovo\\Desktop\\a.txt"));
    18. oos.writeObject(single);
    19. oos.close();
    20. }
    21. }

    通过反序列化的方式创建了多个不同的实例,破坏了单例模式。解决方案是新增readResolve()方法。新增的readResolve()方法已经存在上面的单例模式中。

            2.反射

    1. public class test {
    2. public static void main (String[] args) {
    3. // 1.获取字节码对象
    4. Class clazz = Single.class;
    5. // 2.获取对象的无参构造器
    6. Constructor cons = clazz.getDeclaredConstructor();
    7. // 3.因为无参构造器是私有的,所以取消权限访问的检查
    8. cons.setAccessible(true);
    9. // 4.创建对象
    10. Single s1 = (Single) cons.newInstance();
    11. Single s2 = (Single) cons.newInstance();
    12. System.out.println(s1 == s2);// false
    13. }
    14. }

    Java的反射机制可创建多个实例从而破坏单例模式,解决方案即是使用双重非空判断,加synchronized关键字是应对多线程的情况,所以synchronized+双重非空判断可防止反射破坏单例模式。代码的写法已经在上面的例子中写出来了。

    扩展:

            JDK中 - Runtime类使用的是单例模式(饿汉式)

    1. public class test{
    2. public static void main(String[] args){
    3. Runtime runtime = Runtime.getRuntime();
    4. // 参数是一个命令
    5. Process process = runtime.exec("ipconfig");
    6. InputStream is = process.getInputStream();
    7. byte[] arr = new byte[1024 * 1024 * 100];
    8. // 读取到数据的字节长度
    9. int len = is.read(arr);
    10. // 输入到控制台
    11. System.out.println(new String(arr, 0, len, "GBK"));
    12. }
    13. }

  • 相关阅读:
    第12章 软件测试基础 12.1-软件测试 12.2-验证与确认 12.3-软件缺陷
    SuperViT:Super Vision Transformer
    基于体素场景的摄像机穿模处理
    Java PrintWriter.write()方法具有什么功能呢?
    k8s crd设置额外header
    ATF启动(五):服务注册
    TCP IP网络编程(六) 基于UDP的服务器端、客户端
    如何实现基于 RADIUS 协议的双因子认证 MFA?
    linux 安装 elasticsearch8.5.0
    正则表达式的一些高级用法
  • 原文地址:https://blog.csdn.net/BabyOi/article/details/139602194