九折技术 九折技术
首页
  • Go
  • MIT-6824
  • 算法与数据结构
  • 面向对象
  • 代码整洁
  • 重构
  • 设计模式
  • 学习
  • 技术
  • 人文
关于
  • 网站
  • 资源
  • 分类
  • 标签
  • 归档
GitHub

HoldDie

长期有耐心,一切才刚刚开始!
首页
  • Go
  • MIT-6824
  • 算法与数据结构
  • 面向对象
  • 代码整洁
  • 重构
  • 设计模式
  • 学习
  • 技术
  • 人文
关于
  • 网站
  • 资源
  • 分类
  • 标签
  • 归档
GitHub
  • 代码整洁

  • 重构

  • 设计模式

    • 创建型-单例模式
      • 创建型-工厂模式
      • 创建型-建造者模式
      • 创建型-原型模式
      • 结构型-代理模式
      • 结构型-桥接模式
      • 结构型-装饰器模式
      • 结构型-适配器模式
      • 结构型-门面模式
      • 结构型-组合模式
      • 结构型-享元模式
      • 行为型-观察者模式
      • 行为型-模板模式
      • 行为型-策略模式
      • 行为型-责任链模式
      • 行为型-状态模式
      • 行为型-迭代器模式
      • 行为型-访问者模式
      • 行为型-备忘录模式
      • 行为型-命令模式
      • 行为型-解释器模式
      • 行为型-中介模式
    • 架构
    • 设计模式
    holddie
    2020-08-25

    创建型-单例模式

    # 什么是单例模式?

    • 一个应用中,一个类有且仅有一个实例的情况。
    • 在 Java 中就是每个 JVM,一个实例。
    • 私有化构造器,返回创建好的对象(保持只有一个对象)

    # 为什么要使用单例?

    • 在内存中只有一个实例对象,节省内存空间.避免重复的创建和销毁对象。
    • 可以提高性能,避免对多重资源的重复占用,可以全局进行访问。
    • 使用单例解决资源访问冲突的问题。

    # 如何编写单例?

    # 饿汉模式(简单)

    • 优点
      • 在类加载的时已初始化好,是线程安全的
    • 缺点
      • 对象大时,占用系统资源较多
      • 如果实例占用资源多或初始化耗时长,影响性能
    • 适用
      • 对象不大,占用系统资源较少
    public class Singleton01 {
      private static final Singleton01 INSTANCE = new Singleton01();
    
      private Singleton01() {
      }
    
      public static Singleton01 getInstance() {
        return INSTANCE;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 饿汉模式(静态块)

    public class Singleton02 {
      private static final Singleton02 INSTANCE;
    
      private Singleton02() {
      }
    
      static {
        try {
          INSTANCE = new Singleton02();
        } catch (Exception e) {
          throw new RuntimeException(" error! ");
        }
      }
    
      public static Singleton02 getInstance() {
        return INSTANCE;
      }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 懒汉模式(简单,非安全)

    • 优点
      • 支持延迟加载
    • 缺点
      • 非线程安全
    public class Singleton03 {
      private static Singleton03 INSTANCE;
    
      private Singleton03() {
      }
    
      public static Singleton03 getINSTANCE() {
        if (INSTANCE == null) {
          INSTANCE = new Singleton03();
        }
        return INSTANCE;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    # 懒汉模式(简单,同步)

    public class Singleton04 {
      private static Singleton04 INSTANCE;
    
      private Singleton04() {
      }
    
      private static synchronized Singleton04 getInstance() {
        if (INSTANCE == null) {
          INSTANCE = new Singleton04();
        }
        return INSTANCE;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    # 懒汉模式(双重检测)

    public class Singleton05 {
        private static volatile Singleton05 INSTANCE;
    
        private Singleton05() {
        }
    
        public static Singleton05 getInstance() {
            if (INSTANCE == null) {
                synchronized (Singleton05.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new Singleton05();
                    }
                }
            }
            return INSTANCE;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # Bill Pugh 单例(静态内部类)

    • 优点
      • 即保证了线程安全,又能做到延迟加载
    public class Singleton06 implements Serializable {
      private Singleton06() {
      }
    
    
      private static class LazyHolder {
        private final static Singleton06 INSTANCE = new Singleton06();
      }
    
      public static Singleton06 getInstance() {
        return LazyHolder.INSTANCE;
      }
    
      // 确保反序列化单例
      protected Object readResolve() {
        return getInstance();
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    # 枚举单例

    • 基于枚举类型的单例实现,保证了实例创建的线程安全性和实例的唯一性。
    public enum Singleton07 {
      INSTANCE;
    
      private AtomicLong id = new AtomicLong(0);
    
      public long getId() {
        return id.incrementAndGet();
      }
    }
    
    
    class TestEnumSingleton{
      public static void main(String[] args) {
        System.out.println(Singleton07.INSTANCE.getId());
        System.out.println(Singleton07.INSTANCE.getId());
        System.out.println(Singleton07.INSTANCE.getId());
        System.out.println(Singleton07.INSTANCE.getId());
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 单例存在哪些问题?

    • 单例对 OOP 特性的支持不友好
      • 单例模式对继承、多态特性的支持不太友好,从理论上讲可以实现继承和多态,但是这样的设计会导致代码的可读性变差。
      • 一般选用单例模式,相当于损失了可以应对未来需求变化的扩展性。
    • 单例会隐藏类之间的依赖关系
      • 通过构造函数、参数传递等方式声明的类之间的依赖关系,我们通过查看函数的定义就能容易识别出来。
      • 单例类不需要显示创建、不需要依赖参数传递,在函数中的直接调用就可以,如果代码复杂,这种依赖关系不能很方便了解。
    • 单例对代码的扩展性不友好
      • 虽然生成全局一个对象的实例使用单例模式很棒,但是如果未来业务有所变化,系统需要两个功能类似的类,在整个 JVM 上不好扩展。
    • 单例对代码的可测试性不友好
      • 虽然我们可以使用 Mock 可以对单例进行测试,但是如果单例类持有成员变量,那它实际会被所有代码共享,如果这个全局变量是一个可变变量,那么在并发测试的过程中可能会有不同的结果。
    • 单例不支持有参数的构造函数
      • 因为我们知道,如果我们传递了对函数构造有关的参数,那么可能会导致,每次调用使用不同参数,得到不同的对象,违背的单例的目的。
    • 可通过反射创建对象
      • 解决:在第二次创建对象的时候抛出异常
    • 在序列化和反序列话时不是同一对象
      • 解决:添加单例类添加readResolve()方法

    # 单例模式与静态类的区别?

    • Static 类有更好的访问效率。
    • 单例比静态类更容易测试,单例模式更容易 Mock,可以使用 JUnit 单元测试。
    • 如果需要维护有状态的数据,使用单例比静态类更好。
    • 单例模式支持懒加载,而静态类不支持。
    • 对于一些依赖注入框架,能够很好的管理单例对象。
    • 单例类相比静态类更加面向对象,可以使用继承和多态继承一个基类,实现一个接口,提供不同的功能。
    • Java 源码中的一些实现
      • java.lang.Runtime,该类使用了单例模式。
      • java.lang.Math,该类使用 static 来实现的。

    # 如何理解单例模式中的唯一性?

    • 在一个进程中,一个类只允许撞见唯一一个对象,那么这个类就是一个单例类。
    • 进程之间是不共享地址空间的,如果我们在一个进程中创建另外一个进程,操作系统会给新进程分配新的地址空间,并且将老进程地址空间的所有内容拷贝一份到新进程的地址空间中,这些内容包括代码、数据(临时变量、对象)。

    # 如何实现线程唯一的单例?

    • “进程唯一”指的是进程内唯一,进程间不唯一。
    • “线程唯一”指的是线程内唯一,线程间可以不唯一。
    • 一般对应的实现思路,使用一个 Map,可以是线程ID,value 是对象。
    • Java 语言本事提供了 ThreadLocal 工具类,可以轻松实现线程唯一单例。

    # 如何实现集群环境下的单例?

    • 集群环境的下的单例就是指不同进程间要保证唯一。
    • 一般的实现思路就是需要使用一个公共共享区域存储序列化文件。
    • 具体操作是我们把这个单例对象序列化存储到外部共享存储区,进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存中,并反序列化成对象,然后再使用,使用完成之后还需要存储回外部共享存储区。
    • 为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。

    # 如何实现一个多例模式?

    • 一个类可以创建多个对象,并且创建个数有限,我们称之为多例模式。
    • 一般实现思路也是使用一个 Map ,将对应的不同实例放到其中。
    public class BackendServer {
      private long serverNo;
      private String serverAddress;
      private static final int SERVER_COUNT = 3;
      private static final Map serverInstances = new HashMap<>();
    
      static {
        serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
        serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
        serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
      }
    
      private BackendServer(long serverNo, String serverAddress) {
        this.serverNo = serverNo;
        this.serverAddress = serverAddress;
      }
    
      public BackendServer getInstance(long serverNo) {
        return serverInstances.get(serverNo);
      }
    
      public BackendServer getRandomInstance() {
        Random r = new Random();
        int no = r.nextInt(SERVER_COUNT) + 1;
        return serverInstances.get(no);
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    编辑
    #单例模式
    上次更新: 2020/09/08, 15:09:00
    解耦代码
    创建型-工厂模式

    ← 解耦代码 创建型-工厂模式→

    最近更新
    01
    行为型-访问者模式
    11-24
    02
    行为型-备忘录模式
    11-24
    03
    行为型-命令模式
    11-24
    更多文章>
    Theme by Vdoing | Copyright © 2019-2020 HoldDie | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式