首页 > 其他分享 >设计模式之单例模式

设计模式之单例模式

时间:2023-01-11 08:11:06浏览次数:51  
标签:初始化 Singleton 对象 创建 模式 instance 单例 设计模式

本文已经收录到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

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

new Singleton()会执行三个动作:分配内存空间、初始化对象和对象引用指向内存地址。

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory);  // 2:初始化对象
instance = memory;    // 3:设置instance指向刚分配的内存地址

由于指令重排优化的存在,导致初始化对象和将对象引用指向内存地址的顺序是不确定的。在某个线程创建单例对象时,会为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的是未初始化的对象,程序就会出错。volatile 可以禁止指令重排序,保证了先初始化对象再赋值给instance变量。

Time Thread A Thread B
T1 检查到instance为空
T2 获取锁
T3 再次检查到instance为空
T4 instance分配内存空间
T5 instance指向内存空间
T6 检查到instance不为空
T7 访问instance(此时对象还未完成初始化)
T8 初始化instance

双重检查锁定单例优点

  • 对象的创建是线程安全的。
  • 支持延时加载。
  • 获取对象时不需要加锁。

静态内部类

它与饿汉模式一样,也是利用了类初始化机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

基于类初始化的方案的实现代码更简洁。

public class Instance {
    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }
    private Instance() {}
    public static Instance getInstance() {
        return InstanceHolder.instance ;  // 这里将导致InstanceHolder类被初始化
    }
}

如上述代码,InstanceHolder 是一个静态内部类,当外部类 Instance 被加载的时候,并不会创建 InstanceHolder 实例对象。

只有当调用 getInstance() 方法时,InstanceHolder 才会被加载,这个时候才会创建 InstanceInstance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。

静态内部类单例优点

  • 对象的创建是线程安全的。
  • 支持延时加载。
  • 获取对象时不需要加锁。

枚举

用枚举来实现单例,是最简单的方式。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum Singleton {
  INSTANCE; // 该对象全局唯一
}

最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~

Github地址https://github.com/Tyson0314/java-books

标签:初始化,Singleton,对象,创建,模式,instance,单例,设计模式
From: https://www.cnblogs.com/tyson03/p/17042739.html

相关文章

  • MVC:开发模式
    1.jsp演变历史    1.早期只有servlet,只能使用response输出标签数据,非常麻烦。      2.后来有了jsp,简化了Servlet的开发,如果过度使用jsp中即写大量的Java代码,造......
  • 23种设计模式的优缺点
    【设计模式】23种设计模式的优缺点分析   1、单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。2、原型......
  • Unity中单例模式的优雅实现
    引言系统地整理了下在Unity中实现单例的几种写法。针对两类情况分别提供了实现方案:纯C#实现(7种)继承自MonoBehaviour(3种)分析了各种方案的优劣,记录了思考过程,最后......
  • 【Azure 环境】移动应用 SSO 登录AAD, MSAL的配置为Webview模式时登录页面无法加载
    问题描述移动端集成MASL登录过程中,配置文件中配置项“authorization_user_agent”使用“DEFAULT”可以正常登录,但是改为“WEBVIEW”后就无法登陆,一直处于Loading状态。参......
  • 学习记录-简单工厂模式
    简单工厂模式工厂模式(FactoryPattern)是Java中最常用的设计模式之一。简单工厂模式(SimpleFactoryPattern),又称为静态工厂方法(StaticFactoryMethod)模式,它属于类创建型......
  • 学习记录-单例模式
    单例模式单例模式(SingletonPattern)是Java中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种......
  • 状态机模式
    有限状态机  FSM(FiniteStateMachine)状态机四要素现态、条件、动作、次态现态:是指当前所处的状态条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次......
  • Linux 使用模式匹配文件名批量删除文件
    举例1:删除文件名中包含abc的文件#先列出来确认一下find./dir-typef-name'*abc*'#执行删除命令find./dir-typef-name'*abc*'-delete举例2:删除文件名......
  • 责任链模式
    顾名思义,责任链模式(ChainofResponsibilityPattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行......
  • 【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redi
    redis-full-check的使用背景在经历了之前的文章内容章节内容,已完成Redis迁移后,可能会存在以下问题需要进行数据迁移之后的对比。例如,如果Redis迁移的过程出现异常,源端与目的......