对饿汉和懒汉的提升——双重校验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

相关文章

国产游戏崛起:技术革新与文化自信的双重推动

近年来,国产游戏行业发展迅猛,技术水平和作品质量均得到了显著提升。特别是以《黑神话:悟空》为代表的一系列优秀作品,成功打破了过去中国游戏市场以手游和网游为主的局限,向全球玩家展示了中国在单机游戏领域的实力与潜力。随着中国开发者在画面渲染、物理引擎、AI 技术和服务器架构等方面取得了显著进展,国产游戏正逐步赢得国际市场的认可。然而,面对全球游戏行业的激烈竞争,国产游戏技术依然面临诸多挑战,未来的

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>sp

java学习,进阶,提升

http://how2j.cn/k/hutool/hutool-brief/1930.html?p=73689

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介  1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置,启动方式也不方便需要配置BOOT引脚触发启动  4 IAP(自己写的Bootloader,实现程序升级) 1 比如蓝牙转串口,

JAVA用最简单的方法来构建一个高可用的服务端,提升系统可用性

一、什么是提升系统的高可用性 JAVA服务端,顾名思义就是23体验网为用户提供服务的。停工时间,就是不能向用户提供服务的时间。高可用,就是系统具有高度可用性,尽量减少停工时间。如何用最简单的方法来搭建一个高效率可用的服务端JAVA呢? 停工的原因一般有: 服务器故障。例如服务器宕机,服务器网络出现问题,机房或者机架出现问题等;访问量急剧上升,导致服务器压力过大导致访问量急剧上升的原因;时间和

FreeRTOS内部机制学习03(事件组内部机制)

文章目录 事件组使用的场景事件组的核心以及Set事件API做的事情事件组的特殊之处事件组为什么不关闭中断xEventGroupSetBitsFromISR内部是怎么做的? 事件组使用的场景 学校组织秋游,组长在等待: 张三:我到了 李四:我到了 王五:我到了 组长说:好,大家都到齐了,出发! 秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu

校验码:奇偶校验,CRC循环冗余校验,海明校验码

文章目录 奇偶校验码CRC循环冗余校验码海明校验码 奇偶校验码 码距:任何一种编码都由许多码字构成,任意两个码字之间最少变化的二进制位数就称为数据检验码的码距。 奇偶校验码的编码方法是:由若干位有效信息(如一个字节),再加上一个二进制位(校验位)组成校验码。 奇校验:整个校验码中1的个数为奇数 偶校验:整个校验码中1的个数为偶数 奇偶校验,可检测1位(奇数位)的错误,不可纠错。

C++/《C++为什么要有静态成员函数》

摘要        本文说明了什么是静态成员变量,什么是静态成员函数的概念,讨论了访问私有静态成员变量的三个方法。得出用静态成员函数访问静态私有成员变量是最佳方法即回答了“C++为什么要有静态成员函数“的问题。 类的静态成员 我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员在类的所有对象中是

c++的静态变化!

静态成员   对于非静态成员,一个类的每个对象都自己存有一个副本,每个对象根据自己拥有的非静态的数据成员来区别于其他对象。而静态成员则解决了同一个类的多个对象之间数据和函数的共享问题。   静态数据成员   静态数据成员的作用是:实现同一类的不同对象之间的数据共享。   #include<IOSTREAM>   using namespace std;   class Po