NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 设计模式

    • 介绍与目的
    • 七大设计模式
    • 面向对象编程范式
    • 创造者模式
    • 创建者模式-单例模式
      • 概念
      • 使用场景
      • 代码示例
        • 线程安全
        • 线程不安全
        • JDK:Runtime.class
        • Spring:ReactiveAdapterRegistry.class
        • Tomcat:TomcatURLStreamHandlerFactory.class
      • 知识点
        • 懒汉式
        • synchronized方法
        • synchronized代码块 + 双重校验 + volatile
        • 内部类(延迟加载)
        • 饿汉式
        • 静态变量
        • 枚举
      • 总结
    • 创建者模式-工厂模式
    • 创建者模式-抽象工厂模式
    • 创建者模式-建造者模式
    • 创建者模式-原型模式
    • 结构型模式
    • 代理模式
    • 中介者模式
    • 命令模式
    • 责任链模式
    • 装饰模式
    • 策略模式
    • 适配器模式
    • 迭代器模式
    • 组合模式
    • 观察者模式
    • 外观模式
    • 备忘录模式
    • 访问者模式
    • 状态模式
    • 解释器模式
    • 享元模式
    • 桥接模式
    • 模板方法模式
  • 开发规范

  • 经验分享

  • 记录

  • 快速开始

  • 笔记

  • 面试题

  • 微服务

  • 踩过的坑

  • Java
  • 设计模式
NipGeihou
2022-09-01
目录

创建者模式-单例模式

# 概念

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

# 使用场景

  • 要求生成唯一序列号的环境。
  • 在整个项目中需要一个共享访问点或共享数据,例如一个 Web 页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的。
  • 创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源。
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为 static 的方式)。

# 代码示例

# 线程安全

public class Singleton {
    private static final Singleton singleton = new Singleton();
    //限制产生多个对象
    private Singleton(){
    }
    //通过该方法获得实例对象
    public static Singleton getSingleton(){
        return singleton;
    }
    //类中其他方法,尽量是 static
    public static void doSomething(){
    }
}

# 线程不安全

public class Singleton {
    private static Singleton singleton = null;
    //限制产生多个对象
    private Singleton(){
    }
    //通过该方法获得实例对象
    public static Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

针对线程不安全

在 getSingleton 方法前加 synchronized 关键字,也可以在 getSingleton 方法内增加 synchronized 来实现。

# JDK:Runtime.class

在 JDK 中的 Runtime.class 就使用到了此方式的单例模式。

private static Runtime currentRuntime = new Runtime();

# Spring:ReactiveAdapterRegistry.class

使用了 synchronized 代码块 + 双重校验 + volatile 的懒汉式单例模式

@Nullable
private static volatile ReactiveAdapterRegistry sharedInstance;

public static ReactiveAdapterRegistry getSharedInstance() {
    ReactiveAdapterRegistry registry = sharedInstance;
    if (registry == null) {
        synchronized (ReactiveAdapterRegistry.class) {
            registry = sharedInstance;
            if (registry == null) {
                registry = new ReactiveAdapterRegistry();
                sharedInstance = registry;
            }
        }
    }
    return registry;
}

# Tomcat:TomcatURLStreamHandlerFactory.class

使用了 synchronized 代码块 + 双重校验 + volatile 的懒汉式单例模式

private static volatile TomcatURLStreamHandlerFactory instance = null;

private static TomcatURLStreamHandlerFactory getInstanceInternal(boolean register) {
    // Double checked locking. OK because instance is volatile.
    if (instance == null) {
        synchronized (TomcatURLStreamHandlerFactory.class) {
            if (instance == null) {
                instance = new TomcatURLStreamHandlerFactory(register);
            }
        }
    }
    return instance;
}

# 知识点

# 懒汉式

在首次调用时创建对象,但需要注意线程安全问题。

解决线程安全的方法:

# synchronized 方法

在 getSingleton() 方法上加锁( synchronized )

 






public synchronized static Singleton getSingleton(){
    if(singleton == null){
        singleton = new Singleton();
    }
    return singleton;
}

缺点:锁的粒度过大,导致整个方法,同时只能有一个线程访问。

# synchronized 代码块 + 双重校验 + volatile

 



 
 
 






private volatile static Singleton singleton;

public static Singleton getSingleton(){
    if(singleton == null){
        synchronized(Singleton.class){
            if(singleton == null){
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

在类加载时可能出现重排序,先执行赋值 singleton ,再初始化空间,从而导致其他线程获取时导致空指针或未知问题。可使用 volatile 关键字避免重排序。

参考:3. 类加载过程讲解_哔哩哔哩_bilibili (opens new window)

# 内部类(延迟加载)


 
 
 






class InnerClassSingleton{
    private static class SingletonHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance(){
        return SingletonHolder.instance;
    }
}

实例会在首次调用 getInstance() 时创建。

# 饿汉式

# 静态变量

private static final Singleton singleton = new Singleton();

只有在真正主动使用对应的类时,才会触发初始化:

  • 当前类是启动类即 main 函数所在类
  • 直接进行 new 操作
  • 访问静态属性
  • 访问静态方法
  • 用反射访问类
  • 初始化一个类的子类
  • 等等

类加载的初始化阶段就完成了实例的初始化。本质上就是借助 jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM 以同步的形式来完成类加载的整个过程)。

# 枚举

通过反编译可知,实际上是使用 static 代码块初始化的饿汉式单例模式

# 总结

在被问到设计模式的时候,相信大多数刚接触设计模式的同学,第一个相对的就是单例模式,也可能跟几乎所有的教程第一个设计模式都是讲的单例模式有关。

单例设计模式的思想一句话就是一个类只有一个实例化对象,这些类对象是可以复用的,一个和多个是没有区别的,因此出于节省资源来考虑,使用单例模式更佳。单例模式中的实例化过程,可分为主动(饿汉式)、被动(懒汉式)两种,对性能有较高要求的,被动的懒汉式会更好,只有在调用此对象方法时才会被实例化,但需要处理好线程安全问题。在 Spring 和 Tomcat 的源码中使用到的单例模式,采用 synchronized代码块 + 双重校验 + volatile 的方式,因此在实际开发中使用此方式是一个不错的选择,当然如果这个对象在项目运行周期中,一定会使用的,使用主动的饿汉式创建才也不错的选择(例如 JDK 中的 Runtime.class)。

上次更新: 2022/12/31, 03:04:26
创造者模式
创建者模式-工厂模式

← 创造者模式 创建者模式-工厂模式→

最近更新
01
iSCSI服务搭建
05-10
02
磁盘管理与文件系统
05-02
03
网络测试 - iperf3
05-02
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式