对饿汉和懒汉的提升——双重校验Initialization-on-demand holder idiom(登记式/静态内部类)

本文主要是介绍对饿汉和懒汉的提升——双重校验Initialization-on-demand holder idiom(登记式/静态内部类),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说明

都知道饿汉有内存内存浪费的问题,而懒汉有线程安全问题。所以这两个平时都不敢用,但是它们的优化方式我经常说不明白。今天好好总结总结。

双重校验

是否 Lazy 初始化:是

是否多线程安全:是

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

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

性能优化:只在实例尚未被创建时同步,减少了每次调用getSingleton()方法时的同步开销。一旦实例创建,获取实例的操作就不再需要同步,这对于频繁调用单例实例的场景是一个重要的性能优化。

线程安全:通过双重校验锁的方式,可以确保在多线程环境中单例的唯一性和线程安全性。第一重校验确保只有首次访问单例时才进行同步,第二重校验则是为了确保在进入同步块后,如果有其他线程已经初始化了实例,就避免再次初始化。

volatile关键字的作用:在singleton变量上使用volatile关键字是关键,它确保实例的初始化完整性和可见性。没有volatile,可能出现部分初始化的对象被其他线程使用的情况,因为singleton= new Singleton();这个操作不是原子的,它包括了分配内存、初始化对象、将singleton变量指向分配的内存空间这几个步骤。使用volatile关键字可以防止指令重排,确保这些步骤的执行顺序。

Initialization-on-demand holder idiom(登记式/静态内部类)

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:一般

该模式利用了Java语言规范中保证类的初始化阶段是线程安全的原理。在这种模式中,单例类本身并不直接实例化单例,而是在内部定义一个静态内部类,这个内部类包含有单例对象的实例。当外部类的静态方法(如getInstance())被调用时,内部类才会被加载和初始化,从而创建单例对象。

public class Singleton {// 私有构造函数,防止外部实例化private Singleton() {}// 静态内部类private static class Holder {// 在内部类中持有外部类的实例,并且可被直接初始化private static final Singleton INSTANCE = new Singleton();}// 提供给外部的获取实例的静态方法public static Singleton getInstance() {return Holder.INSTANCE;}
}

而它关键的一点是,类在初始化时是线程安全的。

类加载过程:Java类的加载分为加载(Loading)、链接(Linking)和初始化(Initialization)三个主要阶段。其中初始化阶段是关键,它发生在类首次被使用时。

初始化阶段的线程安全:在初始化阶段,Java虚拟机(JVM)负责处理静态变量的赋值和静态块的执行。JLS规定,这个阶段必须是线程安全的。这意味着如果多个线程同时尝试初始化一个类,JVM会确保该类在任何时候只被一个线程初始化。直到初始化完成,其他线程都会阻塞等待。

静态内部类的特性:静态内部类只有在被使用的时候才会被加载和初始化。这是因为类的初始化是触发在某个类首次被使用时,比如引用静态字段、调用静态方法或者创建类的实例。因此,静态内部类提供了一种延迟初始化对象的方法,同时保持了JVM在类初始化阶段的线程安全性。
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方也称为静态内部类。

static的扩展:
static关键字的作用是把类的成员变成类相关,而不是实例相关,即static修饰的成员属于整个类,而不属于单个对象。外部类的上一级程序单元是包,所以不可使用static修饰;而内部类的上一级程序单元是外部类,使用static修饰可以将内部类变成外部类相关,而不是外部类实例相关。因此static关键字不可修饰外部类,但可修饰内部类。

静态内部类需满足如下规则:

静态内部类可以包含静态成员,也可以包含非静态成员;

静态内部类不能访问外部类的实例成员,只能访问它的静态成员;

外部类的所有方法、初始化块都能访问其内部定义的静态内部类;

在外部类的外部,也可以实例化静态内部类,语法如下:

外部类.内部类 变量名 = new 外部类.内部类构造方法();

单例模式的应用:在单例模式中,这种特性被用于保证单例对象的唯一性和线程安全性。单例类的私有静态内部类持有单例对象的实例。该内部类不会在单例类被加载时立即初始化,而是在首次调用getInstance()方法时,触发内部类的加载和初始化,从而创建单例对象。由于类的初始化是线程安全的,这种方法自然地保证了单例实例的线程安全性,无需额外的同步机制。

在双重校验锁模式中,volatile关键字的两个主要作用

  1. 保证可见性:

    在多线程环境中,一个线程对volatile修饰的变量的修改,对其他线程是立即可见的。这意味着当一个线程修改了单例对象的引用,这个修改对于其他访问该对象的线程是可见的,从而确保了线程之间对单例实例的正确共享。
    防止指令重排序:

  2. 在Java内存模型中,编译器和处理器可能会对指令进行重排序,以提高程序的执行效率。但是,在某些情况下,这种重排序可能会导致程序逻辑上的错误。volatile修饰的变量可以禁止指令重排序。
    在双重校验锁模式下,禁止指令重排序是非常重要的。考虑单例对象的初始化过程,这个过程不是原子的,它可以分为几个步骤:分配内存空间、初始化对象、将对象的引用赋值给变量。如果没有volatile,就可能出现指令重排序,导致其他线程可能访问到一个未完全初始化的对象。
    例如,在双重校验锁的单例模式中,使用volatile可以避免这样的情况:一个线程A执行了单例实例的初始化,但实际上只是分配了内存空间并将地址赋给了引用变量,而对象的构造函数还没有被执行。此时,另一个线程B检查到单例引用不为空,直接返回了这个半初始化的对象,导致出现错误。

静态内部类不适用volatile

类的初始化锁定:当Java类进行初始化时,JVM会对类进行加锁,这个过程是线程安全的。当一个线程正在初始化一个类时,其他线程对该类的首次使用将会阻塞,直到活动线程完成初始化。

没有指令重排序的问题:在Initialization-on-demand
holder模式中,单例的实例是在静态内部类中静态成员的形式创建的。类的初始化阶段会执行静态变量的赋值和静态代码块,这在Java内存模型中是严格定义的,没有指令重排序发生在静态初始化阶段。

性能优化:由于JVM的类初始化机制,该模式本身就是线程安全的,所以没有必要引入volatile关键字。volatile主要是用于确保变量修改的可见性以及禁止指令重排序,但在这个模式中,这些特性是不需要的,因为JVM已经保证了初始化的正确性。

扩展static的使用

  1. 静态变量(Static Variables):

    静态变量是在类加载时初始化的,具体来说是在类被首次使用时,不是在类文件被加载时。类的使用包括创建类的实例、访问类的静态变量或方法。
    静态变量只初始化一次,即在类加载的时候,之后不会再次初始化。

  2. 静态方法(Static Methods):

    静态方法不依赖于类的实例,可以通过类名直接调用。 静态方法可以在类加载后的任何时间被调用。

  3. 静态代码块(Static Blocks):

    静态代码块也是在类加载的时候执行的,且只执行一次。 静态代码块通常用于初始化静态变量或执行仅需进行一次的静态初始化操作。
    如果一个类有多个静态代码块,它们将按照它们在类中出现的顺序被执行。

  4. 静态内部类(Static Inner Classes):

    静态内部类是与外部类关联的一种特殊的内部类,它不依赖于外部类的实例。 静态内部类是在首次使用时加载的,比如创建其实例、访问其静态成员。

这篇关于对饿汉和懒汉的提升——双重校验Initialization-on-demand holder idiom(登记式/静态内部类)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/436532

相关文章

Java利用@SneakyThrows注解提升异常处理效率详解

《Java利用@SneakyThrows注解提升异常处理效率详解》这篇文章将深度剖析@SneakyThrows的原理,用法,适用场景以及隐藏的陷阱,看看它如何让Java异常处理效率飙升50%,感兴趣的... 目录前言一、检查型异常的“诅咒”:为什么Java开发者讨厌它1.1 检查型异常的痛点1.2 为什么说

Springboot项目登录校验功能实现

《Springboot项目登录校验功能实现》本文介绍了Web登录校验的重要性,对比了Cookie、Session和JWT三种会话技术,分析其优缺点,并讲解了过滤器与拦截器的统一拦截方案,推荐使用JWT... 目录引言一、登录校验的基本概念二、HTTP协议的无状态性三、会话跟android踪技术1. Cook

通过配置nginx访问服务器静态资源的过程

《通过配置nginx访问服务器静态资源的过程》文章介绍了图片存储路径设置、Nginx服务器配置及通过http://192.168.206.170:8007/a.png访问图片的方法,涵盖图片管理与服务... 目录1.图片存储路径2.nginx配置3.访问图片方式总结1.图片存储路径2.nginx配置

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束

linux解压缩 xxx.jar文件进行内部操作过程

《linux解压缩xxx.jar文件进行内部操作过程》:本文主要介绍linux解压缩xxx.jar文件进行内部操作,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、解压文件二、压缩文件总结一、解压文件1、把 xxx.jar 文件放在服务器上,并进入当前目录#

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

PowerShell中15个提升运维效率关键命令实战指南

《PowerShell中15个提升运维效率关键命令实战指南》作为网络安全专业人员的必备技能,PowerShell在系统管理、日志分析、威胁检测和自动化响应方面展现出强大能力,下面我们就来看看15个提升... 目录一、PowerShell在网络安全中的战略价值二、网络安全关键场景命令实战1. 系统安全基线核查

Python FastAPI实现JWT校验的完整指南

《PythonFastAPI实现JWT校验的完整指南》在现代Web开发中,构建安全的API接口是开发者必须面对的核心挑战之一,本文将深入探讨如何基于FastAPI实现JWT(JSONWebToken... 目录一、JWT认证的核心原理二、项目初始化与环境配置三、安全密码处理机制四、JWT令牌的生成与验证五、

Java中的内部类和常用类用法解读

《Java中的内部类和常用类用法解读》:本文主要介绍Java中的内部类和常用类用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录内部类和常用类内部类成员内部类静态内部类局部内部类匿名内部类常用类Object类包装类String类StringBuffer和Stri

Spring Validation中9个数据校验工具使用指南

《SpringValidation中9个数据校验工具使用指南》SpringValidation作为Spring生态系统的重要组成部分,提供了一套强大而灵活的数据校验机制,本文给大家介绍了Spring... 目录1. Bean Validation基础注解常用注解示例在控制器中应用2. 自定义约束验证器定义自