Java 设计模式 (一)- 单例模式

本贴最后更新于 1650 天前,其中的信息可能已经事过境迁

Java 设计模式(一)-单例模式

什么是单例模式

  • 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

实现单例模式的思路

  1. 构造方法私有化

    保证系统中只有一个对象的实例,那么我们就需要去防止该对象一不小心被 new 出来,因此我们需要把构造方法私有化

  2. 创建静态方法获取实例

    外界不能通过 new 来获得对象实例了,然而我们需要获取一个实例,那么我们就需要提供一个可供外界访问的返回对象实例的静态方法

  3. 确保对象实例只有一个

    只对对象进行一次实例化,以后的获取都是获取第一次创建的对象实例

几种单例模式的区别

  1. 饿汉模式

    • 饿汉模式是指,我先把对象创建好,等你需要的时候再来拿就可以了
    public class Singleton1 {
      // 无论该对象是否被使用,均会创建,绝大数情况加浪费性能
       private static Singleton1 singleton = new Singleton_1(); 
     private static Singleton1() {
    	  }
     public Singleton1 getSingleton() {
        return singleton;
      }
    }
    
    • 这种模式是最为简单的,而且天生线程安全,但是有一个缺陷在于有资源浪费的嫌疑,不论你什么时候使用,我在类加载的时候就一次性创建了
  2. 懒汉模式

    • 因为饿汉模式存在浪费资源的嫌疑,懒汉模式应运而生
    • 懒汉模式的意思是,我先不创建类的对象实例,等你需要的时候我再创建
    public class Singleton2 {
      private static Singleton2 singleton = null;
      private Singleton2() {}
    
     // 在调用的时候再去创建对象
     // 在并发情况下无法保证项目中只有一个Singleton_2对象,违背单例模式的初衷
     public static Singleton2 getSingleton() {
         if (singleton == null) { // 线程1
             singleton = new Singleton2(); // 线程 2
         }
         return singleton;
     }
    }
    
    • 该对象实例只有在执行获取方法的时候才会去创建、解决了饿汉模式的资源浪费的缺陷
    • 但是上述代码在并发情况下会出现创建多个对象的情况,例如,此时 singleton 为 null,线程 1 与线程 2 均会通过判断,进行对象实例化,因此线程 1 与线程 2 会各自创建一个对象
  3. synchronized 同步锁

    • 因为可能出现外界多人同时访问 getSingleton()方法,可能会出现因为并发问题导致类被实例化多次,我们可以给获取对象方法加上锁 synchronized 来控制该方法在同一时刻只有一个线程能进入,来保证该对象只会被实例化一次
    public class Singleton3 {
    
     private static Singleton3 singleton = null;
    
     private Singleton3() {
     }
    
     // 这是最简单的解决方案,使用synchronized加锁来解决
     // 可以有多个线程进入getSingleton()方法,但只会有一个线程进入if判断,其他线程等待
     public static Singleton3 getSingleton() {
         synchronized (Singleton3.class) {
             if (singleton == null) {
                 singleton = new Singleton3();
             }
         }
         return singleton;
     }
    }
    
    • 我们通过加锁的方式保证了单例模式的安全性,但因为给获取对象的方法进行了加锁,多个线程只有一个能进行判断,其他进程进入阻塞状态,等待上个进程执行结束后才能进行判断,影响性能
  4. 双重检验锁

    • 我们可以使用双重检验锁的形式来优化

      public class Singleton4 {
      
          private static Singleton4 singleton = null;
      
          private Singleton_3() {
          }
          public static Singleton4 getSingleton() {
              // 先进行判断,为null再进入同步代码块
              if (singleton == null) {
                  synchronized (Singleton_3.class) {
                      if (singleton == null) {
                          singleton = new Singleton_3();
                      }
                  }
              }
              return singleton;
          }
      }
      
    • 但是 DCL 也存在有缺陷,由于 jvm 的指令重排序,DCL 偶尔也会存在失效的情况

    • 创建一个对象这个操作并不是一个原子性的操作,jvm 会生成三个指令

      • 指令 1:给对象分配内存空间
      • 指令 2:调用构造器,初始化对象属性
      • 指令 3:将对象引用指向内存地址
    • java 编译器可能会对指令进行优化,可能会把指令执行顺序变为

      • 执行指令 1:分配对象空间
      • 执行指令 3:将对象引用执行内存地址
      • 执行指令 2:调用构造器,初始化对象属性
    • 我们来分析重排序后可能存在的问题

      • 在线程 1 执行到重排序后的第二步之后,然后 cpu 正好切换到线程 2 工作,且线程 2 也正好进行了 getSingleton 操作,此时对象处于创建了引用且分配了内存空间但未进行初始化的状态,那么线程 2 在执行 **if (singleton == null)**判断的时候,线程 2 发现对象不为 null,那么线程 2 就获取到了一个没有进行初始化的对象
    • 所以在这种情况下,双重检验锁的方式会出现 DCL 失效的问题。

    • 优化双重检验锁以及双重检验锁失效的问题非本文重点,有兴趣可参考双重检验锁失效”的问题说明以及相关优化以及翻译版本

  5. 静态内部类

    • 当外部类内被访问时,并不会加载内部类,我们可以通过这一特性来进行设计单例
    public class Singleton4 {
        private Singleton4() {
        }
    
        // 定义静态内部类
        private static class BuildSingleton4 {
            // 加载外部类时不会执行,只有加载 BuildSingleton4 时才会执行
            private static Singleton4 singleton = new Singleton4();
        }
    
        public Singleton4 getSingleton() {
            //当内部类第一次被访问时,创建对象实例
            return BuildSingleton4.singleton;
        }
    }
    
    • 当外部内被访问时,并不会加载内部类,所以只要不访问 BuildSingleton4 这个内部类,private static Singleton4 singleton = new Singleton4()不会被执行,只有当 Singleton4.getSingleton被调用时访问内部类的属性,此时才会将对象进行实例化,这就相当于实现懒加载的效果,这样既解决了恶汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3186 引用 • 8212 回帖
  • 单例模式
    8 引用 • 3 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...