java静态内部类单例模式_单例模式-静态内部类实现及原理剖析

article/2025/9/17 19:29:56

f4f851f629ea93830da9965a12a6727b.png

以我的经验为例(如有不对欢迎指正),在生产过程中,经常会遇到下面两种情况:

1.封装的某个类不包含具有具体业务含义的类成员变量,是对业务动作的封装,如MVC中的各层(HTTPRequest对象以Threadlocal方式传递进来的)。

2.某个类具有全局意义,一旦实例化为对象则对象可被全局使用。如某个类封装了全球的地理位置信息及获取某位置信息的方法(不考虑地球爆炸,板块移动),信息不会变动且可被全局使用。

3.许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。如用来封装全局配置信息的类。

上述三种情况下如果每次使用都创建一个新的对象,且使用频率较高或类对象体积较大时,对象频繁的创建和GC会造成极大的资源浪费,同时不利于对系统整体行为的协调。此时便需要考虑使用单例模式来达到对象复用的目的。

在看单例模式的实现前我们先来看一下使用单例模式需要注意的四大原则:

1.构造私有。(阻止类被通过常规方法实例化)

2.以静态方法或者枚举返回实例。(保证实例的唯一性)

3.确保实例只有一个,尤其是多线程环境。(确保在创建实例时的线程安全)

4.确保反序列化时不会重新构建对象。(在有序列化反序列化的场景下防止单例被莫名破坏,造成未考虑到的后果)

目前单例模式的实现方式有很多种,我们仅讨论接受度最为广泛的DCL方式与静态内部类方式(本篇讨论静态内部类方式)。

静态内部类方式

要理解静态内部类方式,首先要理解类加载机制。

虚拟机把Class文件加载到内存,然后进行校验,解析和初始化,最终形成java类型,这就是虚拟机的类加载机制。加载,验证,准备,解析、初始化这5个阶段的顺序是确定的,类的加载过程,必须按照这种顺序开始。这些阶段通常是相互交叉和混合进行的。解析阶段在某些情况下,可以在初始化阶段之后再开始---为了支持java语言的运行时绑定(动态绑定,多态的原理)。

在Java虚拟机规范中,没有强制约束什么时候要开始加载,但是,却严格规定了几种情况必须进行初始化(加载,验证,准备则需要在初始化之前开始):

1. 遇到 new、getstatic、putstatic、或者invokestatic这4条字节码指令,如果没有类没有进行过初始化,则触发初始化

2. 使用java.lang.reflect包的方法,进行反射调用的时候,如果没有初始化,则先触发初始化

3. 初始化一个类时候,如果发现父类没有初始化,则先触发父类的初始化

我们仅说与本期主题相关的初始化阶段:

类初始化阶段是类加载过程的最后阶段。在这个阶段,java虚拟机才真正开始执行类定义中的java程序代码。在编译的时候,编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。收集完成之后,会编译成java类的static{} 方法,java虚拟机则会保证一个类的static{} 方法在多线程或者单线程环境中正确的执行,并且只执行一次。在执行的过程中,便完成了类变量的初始化。如果我们的java类中,没有显式声明static{}块,如果类中有静态变量,编译器会默认给我们生成一个static{}方法。

对于静态变量来说,虚拟机会保证在子类的static{}方法执行之前,父类的static{}方法已经执行完毕(即如果父类没有加载则先加载父类)。由于父类的static{}方法先执行,也就意味着父类的静态变量要优先于子类的静态变量赋值操作。

对于实例变量来说,在实例化对象时,JVM会在堆中为对象分配足够的空间,然后将空间清零(即所有类型赋默认值,引用类型为null)。JVM会收集类中的复制语句放于构造函数中执行,如果没有显式声明构造函数则会默认生成一个构造函数。子类默认生成的构造函数第一行默认为super();即如果父类有无参的构造方法,子类会先调用父类的构造方法再调用本身的构造方法。因为它继承父类成员的使用,必须先初始化这些成员。如果父类没有无参的构造方法则子类继承会报错,需要子类通过super显式调用父类的有参构造方法。如果类中显式定义一个或多个构造方法,则不再生成默认构造方法。

对于静态变量,上面的描述还不太准确。类初始化阶段,JVM保证同一个类的static{}方法只被执行一次,这是静态内部类单例模式的核心。JVM靠类的全限定类名以及加载它的类加载器来唯一确定一个类。(这个很重要,经常会有这方面的坑!比如反序列化时,被序列化的对象使用java默认的类加载器加载,而使用了反序列化的一方使用的框架(如springBoot就有自己的类加载器)强制使用自己的类加载器加载某个类,则会因为JVM判定不是一个类而报ClassNotFoundException!)

所以修正一下的说法便是,静态内部类单例模式的核心原理为对于一个类,JVM在仅用一个类加载器加载它时,静态变量的赋值在全局只会执行一次!

使用静态内部类的优点是:因为外部类对内部类的引用属于被动引用,不属于前面提到的三种必须进行初始化的情况,所以加载类本身并不需要同时加载内部类。在需要实例化该类是才触发内部类的加载以及本类的实例化,做到了延时加载(懒加载),节约内存。同时因为JVM会保证一个类的()方法(初始化方法)执行时的线程安全,从而保证了实例在全局的唯一性。

下面我们来实现一下静态内部类的单例模式:

/*** @Author Nyr

* @Date 2019/11/19 20:48

* @Description 单例模式-静态内部类方式*/

public classCar2 {privateCar2(){}private static classinnerCar2{private static Car2 car2=newCar2();

}publicCar2 getCar2(){returninnerCar2.car2;

}

}

为什么使用内部类而不是直接使用静态变量,我觉着有两个原因(求指正,第二条并不是很确定,后续会写代码测试):

1. 使用内部类可以延时加载。如果直接使用静态变量,因为加载子类等其它原因对实例进行了初始化,而此时并不需要该类的实例,造成了资源的浪费。

2. 原类因为带有业务含义,在使用上会有各种可能,比如使用了特定的类加载器进行加载,这样就对单例造成了破坏。

说完了优点我们再来说说缺点,那就是内部类的传参不是很灵活,需要将参数定义为final。当然我们也可以将其写入final的Object数组或者在内部类定义一个接受参数的init()方法来接收参数,但总的来说传参确实不方便。

而对上一篇所说的DCL来说,同步块的使用明显的降低了效率。两种方法可以说各有优缺,我们应视实际情况酌情选择。


http://chatgpt.dhexx.cn/article/k0P0ChM6.shtml

相关文章

C# 静态内部类单例模式-静态变量何时初始化

对于一个类的静态变量何时初始化&#xff0c;大家都有一个普遍的共识&#xff0c;那就是第一次使用该类时&#xff0c;初始化该类的所有静态变量和静态方法。 /// <summary>/// 只有在第一次使用到Test1的时候&#xff0c;才会初始化Test1.x/// </summary>class Te…

静态内部类实现单例_单例模式详解

概述 单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。 单例应用场景 Windows系统的任务管理器。Windows系统的回收站。操作系统的文件系统,一个操作系统只能有一个文件系…

使用静态内部类单例模式创建自定义线程池

一、使用场景 1、有时候业务上A端和B端做接口传输消息&#xff0c;B端收到消息后做进一步数据处理&#xff08;持久化或者解析&#xff09;等耗时的操作&#xff0c;如果是同步操作会造成等待、超时等情况。可以先向A端返回一个收到信息的消息&#xff0c;再多线程异步处理数据…

Kotlin 静态内部类单例模式的正确实现方式

本篇是对现网上流传的 Kotlin 实现静态内部类单例模式的纠正&#xff0c;为了把原理说清楚&#xff0c;文章前奏可能会有些长&#xff0c;熟悉静态内部类单例模式原理的朋友&#xff0c;可以直接跳转到文章最后&#xff0c;直接看结果即可。 最近在整理基础库的时候&#xff0c…

设计模式3:单例模式:静态内部类单例模式

单例模式最简单的写法就是静态内部类单例模式&#xff0c;如下&#xff1a; public class Manager {private static class ManagerHolder {private static Manager instance new Manager();}private Manager() {}public static Manager getInstance() {return ManagerHolder.i…

单例模式详解

微信搜索【程序员囧辉】&#xff0c;关注这个坚持分享技术干货的程序员。 概述 单例模式&#xff0c;是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中&#xff0c;使用单例模式的类只有一个对象实例。 单例应用场景 Wi…

MATLAB中的结构体数组(struct)学习笔记

不要失却热情&#xff0c;不要丢掉冠军的心&#xff01; MALAB中的结构体&#xff08;struct&#xff09;数组学习笔记 前言1. 版本2. 关键词 一、Struct结构体数组概述二、Struct结构体数组基本用法1. 结构体的创建2. 结构体中的筛选操作 前言 MATLAB中结构体数组基本用法笔…

MATLAB学习——结构体类型

前言 MATLAB 中的数据类型主要包括数值类型、 逻辑类型、 字符串、函数句柄、 结构体和单元数组类型。 这6种基本的数据类型都是按照数组形式存储和操作的。 一、结构体类型 MATLAB中的结构体与C语言中的结构体类似&#xff0c; 一个结构体可以通过字段存储多个不同类型的数…

Matlab将结构体struct字段内的数据转化到矩阵中

假设structure1,为一结构体&#xff0c;structure1.name为100个字符串 怎么将这些字符串不用循环一次性赋值到矩阵A?? Astructure1.name 为什么只是将第一个赋值过去&#xff1f; 答案是可以使用cat函数&#xff1a; 可以用cat函数&#xff0c; A cat(1,structur1.name)是…

Matlab:结构体数组

Matlab:结构体数组 创建标量结构体访问字段中的值对非标量结构体数组进行索引当您有要按名称组织的数据时,可以使用结构体来存储这些数据。结构体将数据存储在名为字段的容器中,然后您可以按指定的名称访问这些字段。使用圆点表示法创建、分配和访问结构体字段中的数据。如果…

Matlab-结构数组

1 认识结构数组 结构也是一种数据类型&#xff0c;它的每一个元素都有一个名字。称结构中的元素为域。 类似与C语言中的结构体。 2 创建 两种方法&#xff1a; &#xff08;1&#xff09;用赋值语句创建 &#xff08;2&#xff09;用函数 struct 函数进行创建 2.1 赋值语…

Matlab遇到结构体内容引用自非结构体数组对象

原因&#xff1a; 未初始化下一级结构体的值&#xff0c;如果直接调用就会报错。 举例&#xff1a; a.p[]; a.p.x解决方法&#xff1a; 需要先初始化&#xff0c;再调用 a.p.x[] or a.p.x0

Matlab:结构体Struct

Matlab中创建一个结构体数组的方式有两种&#xff0c;分别为直接引用方式和使用struct函数。 1、使用直接引用方式创建结构体 与建立数值型数组一样&#xff0c;建立新struct对象不需要事先申明&#xff0c;可以直接引用&#xff0c;而且可以动态扩充。比如建立一个复数变量x…

Matlab 结构体(struct)使用

转自http://blog.sina.com.cn/s/blog_468651400100c6c0.html 要在MALTAB中实现比较复杂的编程&#xff0c;就不能不用struct类型。而且在 MATLAB中实现struct比C中更为方便。 4. 3.1 结构数组的创建 MATLAB提供了两种定义结构的方式&#xff1a;直接应用和使用struct函数。 1. …

MATALB-结构体

结构体 结构体的生成一、 直接输入二、使用结构体生成函数struct 结构体的操作添加成员变量删除成员变量调用成员变量 结构体是另一种可以将不同类型数据组合在一起的数据类型。 MATLAB结构体变量类似于C语言结构体变量,且比C语音更加直观。 结构体与单元数组的区别为,结构体…

matlab学习-结构体变量

matlab结构体学习 matlab结构体学习 matlab结构体与C语言数据结构结构体或java语言类的定义有异曲同工之妙&#xff0c;其主要知识点如下 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 matlab结构体学习前言一…

matlab-结构体struct

在前面&#xff0c;有用到一个cell2struct&#xff08;&#xff09;函数&#xff0c;cell是单元数组&#xff0c;而struct就是结构体。 1.建立结构体&#xff0c;在matlab中建立结构体有两种方式&#xff0c; eg&#xff1a;第一种为直接赋值 >> stu(1).namezhangsan;…

matlab中结构体使用方法

转自 http://hi.baidu.com/dess2211/blog/item/bb9b80185a7f940334fa417c.html matlab中使用结构体 2008-01-15 14:23 结构(struct)数组 要在MALTAB中实现比较复杂的编程&#xff0c;就不能不用struct类型。而且在 MATLAB中实现struct比C中更为方便。 4. 3.1 结构数组的创建 MA…

Matlab中结构体struct创建和使用

在项目上遇见了调用api接口&#xff0c;接口返回的信息为struct&#xff0c;故探讨一下matlab的struct结构体 1、struct结构体创建 创建结构体数组有两种方式&#xff0c;分别为直接创建和使用struct函数 1.1 直接创建 直接定义字段&#xff0c;像使用一般matlab变量一样&…

matlab 结构体

在前面&#xff0c;有用到一个cell2struct&#xff08;&#xff09;函数&#xff0c;cell是单元数组&#xff0c;而struct就是结构体。 1.建立结构体&#xff0c;在matlab中建立结构体有两种方式&#xff0c; eg&#xff1a;第一种为直接赋值 >> stu(1).namezhangsan; …