设计模式之单例模式

2023-01-11 00:00:00 创建 线程 对象 初始化 实例

本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

Github地址:https://github.com/Tyson0314/Java-learning

单例模式

单例模式(Singleton),目的是为了保证在一个进程中,某个类有且仅有一个实例。

由于这个类只有一个实例,所以不能让调用方使用new Xxx()来创建实例。所以,单例的构造方法必须是private,这样就防止了调用方自己创建实例。

单例模式的实现需要三个必要的条件

  1. 单例类的构造函数必须是私有的,这样才能将类的创建权控制在类的内部,从而使得类的外部不能创建类的实例。
  2. 单例类通过一个私有的静态变量来存储其实例。
  3. 单例类通过提供一个公开的静态方法,使得外部使用者可以访问类的实例。

另外,实现单例类时,还需要考虑三个问题:

  • 创建单例对象时,是否线程安全
  • 单例对象的创建,是否延时加载
  • 获取单例对象时,是否需要加锁

下面介绍几种实现单例模式的方式。

饿汉模式

JVM在类的初始化阶段,会执行类的静态方法。在执行类的初始化期间,JVM会去获取Class对象的锁。这个锁可以同步多个线程对同一个类的初始化。

饿汉模式只在类加载的时候创建一次实例,没有多线程同步的问题。单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton() {}  
    public static Singleton newInstance() {
        return instance;  
    }  
}

饿汉式单例的优点

  • 单例对象的创建是线程安全的;
  • 获取单例对象时不需要加锁

饿汉式单例的缺点:单例对象的创建,不是延时加载

懒汉式

与饿汉式思想不同,懒汉式支持延时加载,将对象的创建延迟到了获取对象的时候。不过为了线程安全,在获取对象的操作需要加锁,这就导致了低性能。

public class Singleton { 
  private static final Singleton instance;
  
  private Singleton () {}
  
  public static synchronized Singleton getInstance() {    
    if (instance == null) {      
      instance = new Singleton();    
    }    

    return instance;  
  }
}

上述代码加的锁只有在次创建对象时有用,而之后每次获取对象,其实是不需要加锁的(双重检查锁定优化了这个问题)。

懒汉式单例优点

  • 对象的创建是线程安全的。
  • 支持延时加载。

懒汉式单例缺点

  • 获取对象的操作被加上了锁,影响了并发性能。

双重检查锁定

双重检查锁定将懒汉式中的 synchronized 方法改成了 synchronized 代码块。如下:

public class Singleton {  
    private static volatile Singleton instance = null;  //volatile
    private Singleton(){}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}  

双重校验锁先判断 instance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。

instance使用static修饰的原因:getInstance为静态方法,因为静态方法的内部不能直接使用非静态变量,只有静态成员才能在没有创建对象时进行初始化,所以返回的这个实例必须是静态的。

为什么两次判断instance == null

TimeThread AThread B
T1检查到instance为空
T2检查到instance为空
T3初始化对象A
T4返回对象A
T5初始化对象B
T6返回对象B

相关文章