• Java设计模式


    单例模式我在上学期看一些资料时候学习过,没想到这学期的软件体系结构就有设计模式学习,不过看似篇幅不大,介绍得比较简单,在这里我总结下单例模式,一来整理之前的笔记,二来也算是预习复习课程了。

    概述

    单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一,属于一种创建型模式。

    单例模式保证对于每一个类加载器,一个类仅有一个唯一的实例对象,并提供一个全局的唯一访问点。

    优缺点

    优点:

    1. 减少内存开销。
    2. 避免对资源的多重占用。
    3. 严格控制客户程序访问其唯一的实例。
    4. 单例类的子类都是单例类。
    5. 比较容易改写为允许一定数目对象的类。

    缺点:

    1. 不适用于变化的对象。
    2. 由于没有抽象层,难以扩展。
    3. 职责过重,一定程度上违背了“单一职责原则”。

    核心思路

    1. 构造器私有化。
    2. 私有的静态常量成员。
    3. 公开的静态方法getInstance(),返回静态实例对象。
    4. 确保每次访问的实例对象都是同一个对象。
    5. 多线程中要保证线程安全。

    实现方式

    实现单例方式有五种:饿汉式、懒汉式、双重校验锁(DCL)、静态内部类、枚举。

    饿汉式

    优点:在类加载的时候就完成实例化,避免了线程同步问题。

    缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

    public class Singleton1 {
        private static final Singleton1 INSTANCE = new Singleton1();
        
        private Singleton1() {
            System.out.println("饿汉式,可用");
        }
        
        public static Singleton1 getInstance() {
            return INSTANCE;
        }
    }
    

    懒汉式

    优点:达到了懒加载的效果。

    缺点:线程不安全。

    1. 对于普通懒汉式,线程不安全。

      原因:在进入if (instance == null) 时,可能一个线程进入后就切换到了另一个线程,而此时并未创建实例对象,这个线程又再次进入了if代码块。

      public class Singleton2 {
          private static Singleton2 instance;
          
          private Singleton2() {
              System.out.println("懒汉式,线程不安全,多线程不可用");
          }
          
          public static Singleton2 getInstance() {
              if (instance == null) {
                  instance = new Singleton2();
              }
              return instance;
          }
      }
      
    2. 对方法添加synchronized保证线程安全。

      synchronized保证了getInstance()线程安全,但对方法进行同步效率不高。

      public class Singleton3 {
          private static Singleton3 instance;
      
          private Singleton3() {
              System.out.println("懒汉式(synchronized方法),效率太低");
          }
      
          public static synchronized Singleton3 getInstance() {
              if (instance == null) {
                  instance = new Singleton3();
              }
              return instance;
          }
      }
      
    3. 改进方案2,使用synchronized代码块。

      使用了synchronized代码块改进,但又出现了和方案1一样的线程安全问题。

      public class Singleton4 {
          private static Singleton4 instance;
      
          private Singleton4() {
              System.out.println("懒汉式(synchronized代码块),线程不安全,多线程不可用");
          }
      
          public static Singleton4 getInstance() {
              if (instance == null) {
                  synchronized (Singleton4.class) {
                      // 此处若有线程阻塞,其它线程就仍可以进入到了前面if
                      instance = new Singleton4();
                  }
              }
              return instance;
          }
      }
      

    双重校验锁(DCL)

    针对上一个改进,使用两个if (instance == null)

    这里先别管实现的Serializable接口和readResolve()方法,这两个用于后面的序列化测试。

    另外,此处的volatile声明是很重要的,volatile变量有两种特性。

    一是保证了此变量对所有线程的可见性。“可见性”指的是当一条线程修改了这个变量的值,新值对于其它线程来说是可以立即知道的。

    二是禁止指令重排序优化。但volatile变量的运行在并发编程下并非是安全的,因为不能保证原子性。详细请自己去看相关资料。

    import java.io.Serializable;
    
    public class Singleton5 implements Serializable {
        private static final long serialVersionUID = 1L;
        private static volatile Singleton5 instance;
    
        private Singleton5() {
            System.out.println("懒汉式优化(Double Check Lock)双重校验锁,推荐用");
        }
    
        public static Singleton5 getInstance() {
            if (instance == null) {
                // 未被初始化,但是无法确定这时其他线程是否已经对其初始化,因此添加对象锁进行互斥
                synchronized (Singleton5.class) {
                    // 再一次进行检查,因为有可能在当前线程阻塞的时候,其他线程对instance进行初始化
                    if (instance == null) {
                        // 此时还未被初始化的话,在这里初始化可以保证线程安全
                        instance = new Singleton5();
                    }
                }
            }
            return instance;
        }
    
        // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
        private Object readResolve() {
            return getInstance();
        }
    }
    

    静态内部类

    静态内部类方式在类被加载时并不会立即实例化。

    而是在需要实例化时,调用getInstance()方法,才会装载内部类,从而完成对象实例化。

    import java.io.Serializable;
    
    public class Singleton6 implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private Singleton6() {
            System.out.println("饿汉式优化(静态内部类),推荐用");
        }
    
        private static class SingletonInstance {
            private static final Singleton6 INSTANCE = new Singleton6();
        }
    
        public static Singleton6 getInstance() {
            return SingletonInstance.INSTANCE;
        }
    
        // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
        private Object readResolve() {
            return getInstance();
        }
    }
    

    枚举

    枚举方法不仅保证了线程安全问题,还提供了序列化机制。

    public enum Singleton7 {
        INSTANCE;
    
        private Singleton7() {
            System.out.println("枚举,最好方法");
        }
    
        public static Singleton7 getInstance() {
            return INSTANCE;
        }
    }
    

    测试

    线程安全测试

    public class TestSafety {
        static Singleton1 s1_a;
        static Singleton1 s1_b;
        static Singleton2 s2_a;
        static Singleton2 s2_b;
        static Singleton3 s3_a;
        static Singleton3 s3_b;
        static Singleton4 s4_a;
        static Singleton4 s4_b;
        static Singleton5 s5_a;
        static Singleton5 s5_b;
        static Singleton6 s6_a;
        static Singleton6 s6_b;
        static Singleton7 s7_a;
        static Singleton7 s7_b;
    
        public static void getS1_A() {
            s1_a = Singleton1.getInstance();
        }
    
        public static void getS1_B() {
            s1_b = Singleton1.getInstance();
        }
    
        public static void getS2_A() {
            s2_a = Singleton2.getInstance();
        }
    
        public static void getS2_B() {
            s2_b = Singleton2.getInstance();
        }
    
        public static void getS3_A() {
            s3_a = Singleton3.getInstance();
        }
    
        public static void getS3_B() {
            s3_b = Singleton3.getInstance();
        }
    
        public static void getS4_A() {
            s4_a = Singleton4.getInstance();
        }
    
        public static void getS4_B() {
            s4_b = Singleton4.getInstance();
        }
    
        public static void getS5_A() {
            s5_a = Singleton5.getInstance();
        }
    
        public static void getS5_B() {
            s5_b = Singleton5.getInstance();
        }
    
        public static void getS6_A() {
            s6_a = Singleton6.getInstance();
        }
    
        public static void getS6_B() {
            s6_b = Singleton6.getInstance();
        }
    
        public static void getS7_A() {
            s7_a = Singleton7.getInstance();
        }
    
        public static void getS7_B() {
            s7_b = Singleton7.getInstance();
        }
    
        public static void main(String[] args) {
            Thread t1_a = new Thread(TestSafety::getS1_A);
            Thread t1_b = new Thread(TestSafety::getS1_B);
            t1_a.start();
            t1_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s1_a == s1_b ? " + (s1_a == s1_b));
            System.out.println();
    
            Thread t2_a = new Thread(TestSafety::getS2_A);
            Thread t2_b = new Thread(TestSafety::getS2_B);
            t2_a.start();
            t2_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s2_a == s2_b ? " + (s2_a == s2_b));
            System.out.println();
    
            Thread t3_a = new Thread(TestSafety::getS3_A);
            Thread t3_b = new Thread(TestSafety::getS3_B);
            t3_a.start();
            t3_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s3_a == s3_b ? " + (s3_a == s3_b));
            System.out.println();
    
            Thread t4_a = new Thread(TestSafety::getS4_A);
            Thread t4_b = new Thread(TestSafety::getS4_B);
            t4_a.start();
            t4_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s4_a == s4_b ? " + (s4_a == s4_b));
            System.out.println();
    
            Thread t5_a = new Thread(TestSafety::getS5_A);
            Thread t5_b = new Thread(TestSafety::getS5_B);
            t5_a.start();
            t5_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s5_a == s5_b ? " + (s5_a == s5_b));
            System.out.println();
    
            Thread t6_a = new Thread(TestSafety::getS6_A);
            Thread t6_b = new Thread(TestSafety::getS6_B);
            t6_a.start();
            t6_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s6_a == s6_b ? " + (s6_a == s6_b));
            System.out.println();
    
            Thread t7_a = new Thread(TestSafety::getS7_A);
            Thread t7_b = new Thread(TestSafety::getS7_B);
            t7_a.start();
            t7_b.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("s7_a == s7_b ? " + (s7_a == s7_b));
            System.out.println();
        }
    }
    

    输出结果:

    饿汉式,可用
    s1_a == s1_b ? true
    
    懒汉式,线程不安全,多线程不可用
    懒汉式,线程不安全,多线程不可用
    s2_a == s2_b ? false
    
    懒汉式(synchronized方法),效率太低
    s3_a == s3_b ? true
    
    懒汉式(synchronized代码块),线程不安全,多线程不可用
    懒汉式(synchronized代码块),线程不安全,多线程不可用
    s4_a == s4_b ? false
    
    懒汉式优化(Double Check Lock)双重校验锁,推荐用
    s5_a == s5_b ? true
    
    饿汉式优化(静态内部类),推荐用
    s6_a == s6_b ? true
    
    枚举,最好方法
    s7_a == s7_b ? true
    

    序列化测试

    只对双重校验锁、静态内部类、枚举测试。

    默认只有枚举能保证序列化后对象仍相等,其它需要加readResolve()方法才能。

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class TestSerializable {
        // 测试序列化后的对象是否相等
        public static void main(String[] args) {
            Singleton5 s5_a = Singleton5.getInstance();
            Singleton5 s5_b = Singleton5.getInstance();
            System.out.println("序列化前:s5_a == s5_b ? " + (s5_a == s5_b));
            Path file5 = Paths.get("object5.txt");
            try (ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(file5.toFile()))) {
                out.writeObject(s5_a);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try (ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(file5.toFile()))) {
                s5_b = (Singleton5) in.readObject();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("序列化后:s5_a == s5_b ? " + (s5_a == s5_b));
            System.out.println();
    
            Singleton6 s6_a = Singleton6.getInstance();
            Singleton6 s6_b = Singleton6.getInstance();
            System.out.println("序列化前:s6_a == s6_b ? " + (s6_a == s6_b));
            Path file6 = Paths.get("object6.txt");
            try (ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(file6.toFile()))) {
                out.writeObject(s6_a);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try (ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(file6.toFile()))) {
                s6_b = (Singleton6) in.readObject();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("序列化后:s6_a == s6_b ? " + (s6_a == s6_b));
            System.out.println();
    
            Singleton7 s7_a = Singleton7.getInstance();
            Singleton7 s7_b = Singleton7.getInstance();
            System.out.println("序列化前:s7_a == s7_b ? " + (s7_a == s7_b));
            Path file7 = Paths.get("object7.txt");
            try (ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(file7.toFile()))) {
                out.writeObject(s7_a);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try (ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(file7.toFile()))) {
                s7_b = (Singleton7) in.readObject();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("序列化后:s7_a == s7_b ? " + (s7_a == s7_b));
            System.out.println();
        }
    }
    

    先把readResolve()方法注释掉;输出结果为:

    懒汉式优化(Double Check Lock)双重校验锁,推荐用
    序列化前:s5_a == s5_b ? true
    序列化后:s5_a == s5_b ? false
    
    饿汉式优化(静态内部类),推荐用
    序列化前:s6_a == s6_b ? true
    序列化后:s6_a == s6_b ? false
    
    枚举,最好方法
    序列化前:s7_a == s7_b ? true
    序列化后:s7_a == s7_b ? true
    

    加上readResolve()方法,枚举不需要,枚举甚至连Serializable接口都不需实现;输出结果如下:

    懒汉式优化(Double Check Lock)双重校验锁,推荐用
    序列化前:s5_a == s5_b ? true
    序列化后:s5_a == s5_b ? true
    
    饿汉式优化(静态内部类),推荐用
    序列化前:s6_a == s6_b ? true
    序列化后:s6_a == s6_b ? true
    
    枚举,最好方法
    序列化前:s7_a == s7_b ? true
    序列化后:s7_a == s7_b ? true
    

    使用场景

    • 文件管理系统
    • 日志记录类
    • 与数据库的连接

    应用实例

    java.lang.Runtime#getRuntime()



    ┆ 然 ┆   ┆   ┆   ┆ 可 ┆   ┆   ┆ 等 ┆ 暖 ┆
    ┆ 而 ┆ 始 ┆   ┆   ┆ 是 ┆ 将 ┆   ┆ 你 ┆ 一 ┆
    ┆ 你 ┆ 终 ┆ 大 ┆   ┆ 我 ┆ 来 ┆   ┆ 如 ┆ 暖 ┆
    ┆ 没 ┆ 没 ┆ 雁 ┆   ┆ 在 ┆ 也 ┆   ┆ 试 ┆ 这 ┆
    ┆ 有 ┆ 有 ┆ 也 ┆   ┆ 这 ┆ 会 ┆   ┆ 探 ┆ 生 ┆
    ┆ 来 ┆ 来 ┆ 没 ┆   ┆ 里 ┆ 在 ┆   ┆ 般 ┆ 之 ┆
    ┆   ┆   ┆ 有 ┆   ┆   ┆ 这 ┆   ┆ 降 ┆ 凉 ┆
    ┆   ┆   ┆ 来 ┆   ┆   ┆ 里 ┆   ┆ 临 ┆ 薄 ┆
  • 相关阅读:
    java常用问题排查工具
    一次CMS GC问题排查过程(理解原理+读懂GC日志)
    nginx [alert] 12339#0: 1024 worker_connections are not enough
    netstat Recv-Q和Send-Q
    Use of Recv-Q and Send-Q
    LoadRunner 11 error:Cannot initialize driver dll
    perf + Flame Graph火焰图分析程序性能
    nginx 499状态码
    supervisor管理nginx
    supervisor管理php-fpm
  • 原文地址:https://www.cnblogs.com/qiu_jiaqi/p/Design-Patterns-Singleton.html
Copyright © 2020-2023  润新知