首页 > 其他分享 >单例bean与类加载过程

单例bean与类加载过程

时间:2023-05-31 16:11:55浏览次数:20  
标签:初始化 INSTANCE bean static 单例 Singleton1 public 加载

构造单例bean的方式有很多种,我们来看一下其中一种,饿汉式

public class Singleton1 implements Serializable {
    //1、构造函数私有
    private Singleton1() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }
//2、创建静态常量对象,Instance
    private static final Singleton1 INSTANCE = new Singleton1();
//3、使用getInstance()获取对象
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

    public Object readResolve() {
        return INSTANCE;
    }
}

其保证了单例bean的特性如下:

1、构造函数私有

2、创建静态常量对象,Instance

3、使用getInstance()获取对象

并且对单例bean被破坏进行了防范:

  • 构造方法抛出异常是防止反射破坏单例
  • readResolve() 是防止反序列化破坏单例

目前来看,都没什么问题,但是如果我想创建两个静态变量 a与b呢,并且在new的时候对a,b进行++,会发生什么?

代码如下:

public class Singleton1 implements Serializable {
    //1、构造函数私有
    private Singleton1() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        //调用构造函数时会对a与b进行++;
        a++;
        b++;
        System.out.println("private Singleton1()");
    }
    //2、创建静态常量对象,Instance
    private static final Singleton1 INSTANCE = new Singleton1();
    public static int a;
    public static int b=0;
    //3、使用getInstance()获取对象
    public static Singleton1 getInstance() {

        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

    public Object readResolve() {
        return INSTANCE;
    }
}

这时如果我想对a与b进行输出,最终a、b的值会是多少?

public class TestCase {

    public static void main(String[] args) {
        Singleton1 instance = Singleton1.getInstance();
        System.out.println("a="+instance.a+"  b="+instance.b);

    }
}

想必很多人会认为答案是a与b各自+1;输出 a=1 b=1;

但是真相却是:

private Singleton1()
a=1  b=0

这时为什么呢?

这就与类加载机制有关了,首先我们得知道类加载过程,在类初始化之前,会有一个链接阶段,其中有一个步骤叫做准备

而在准备阶段将会:

  1. 为类变量(static变量)分配内存并且设置该类变量的默认初始值,即零值
  2. 这里不包含用final修饰的static,因为final在编译的时候就会分配好了默认值,准备阶段会显式初始化
  3. 注意:这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中

所以a与b在准备阶段被默认初始值为0;

接下来会进行类初始化,而类初始化的时机则有如下7种:

  1. 创建类的实例
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(比如:Class.forName(“com.atguigu.Test”))
  5. 初始化一个类的子类
  6. Java虚拟机启动时被标明为启动类的类
  7. JDK7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化

很显然我们执行Singleton1 instance = Singleton1.getInstance();时,便对应上面第2点。所以会进行类初始化。

而在类初始化阶段也就是clinit():

  1. 初始化阶段就是执行类构造器方法<clinit>()的过程

  2. 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。也就是说,当我们代码中包含static变量的时候,就会有clinit方法

  3. <clinit>()方法中的指令按语句在源文件中出现的顺序执行

  4. <clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>()

  5. 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕

  6. 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁

可以看到第3点,会跟据源文件种的出现顺序执行:

我们再看看最开始的代码中静态常量与变量的先后顺序:

  private static final Singleton1 INSTANCE = new Singleton1();
    public static int a;
    public static int b=0;

相比大家已经恍然大悟,对于答案也呼之欲出了。

没错,当在准备阶段,a,b将会被赋默认值为0,而当我们调用getInstance()时,就会触发类加载的过程,按照源码的先后顺序,先执行new Singleton1(),将会对a与b进行++,所以a与b分别为1。之后继续顺序执行,int a;不会改变a的值,而b=0,则重新将b从1覆盖为0了。所以最终我们显示a=1 b=0;

那么如何解决呢?

没错!只要修改一下变量声明的顺序,将a与b声明在INSTANCE之前,就不会出现a,b数据不一致的问题了!

谢谢大家阅读,才疏学浅,望多多指教!

标签:初始化,INSTANCE,bean,static,单例,Singleton1,public,加载
From: https://www.cnblogs.com/BestJaxXu/p/17446430.html

相关文章

  • 一个由于不同微服务框架混搭导致BeanPostProcessors处理bean异常导致的问题
        前天到昨天晚上,某开发报告了一个问题,我们的一个应用程序接入了腾讯的TSF微服务框架后,使用feign访问接口,会导致token丢失,无法解决。    大体介绍下项目情况,我们的应用使用了某第三方微服务框架,不是源生的springcloud或springcloudalibaba框架,第三方厂家基于s......
  • r3f加载IFC模型
    import{IFCLoader}from"web-ifc-three";import{IFCSPACE}from"web-ifc";import{useRef}from"react";import{useEffect}from"react";asyncfunctioninit(group){//SetupIFCLoaderconstifcLoader......
  • Java中枚举类的特殊用法-使用枚举实现单例模式和策略模式
    场景设计模式-单例模式-饿汉式单例模式、懒汉式单例模式、静态内部类在Java中的使用示例:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127555096设计模式-单例模式-注册式单例模式-枚举式单例模式和容器式单例模式在Java中的使用示例:https://blog.csdn.net/BAD......
  • Spring AOP错误:org.springframework.beans.factory.BeanNotOfRequiredTypeException:
    org.springframework.beans.factory.BeanNotOfRequiredTypeException:Beannamed'myCalculator'isexpectedtobeoftype'com.mashibing.service.MyCalculator'butwasactuallyoftype'com.sun.proxy.$Proxy19'atorg.springframew......
  • 浅谈 Spring Bean 的生命周期
    一、Bean的生命周期概述区别于普通的Java对象需要通过new创建对象,Spring的Bean由IoC容器进行实例化、组装以及管理的。也就是说 Bean的生命周期完全由IoC容器控制。Spring容器只能管理 单例(singleton) 作用域的Bean的完整生命周期,对于 原型(prototype) 作用域......
  • Could not autowire. No beans of 'AddressBookService' type found.
    错误:错误原因:Service实现类未继承Service接口解决方法: ......
  • SimpleAdmin手摸手教学之:基于Ant Design Tree组件实现树形结构数据的异步加载
    一、说明当有一个树形结构的数据有非常多个节点的时候,一次性加载所有节点会显得过于臃肿,可能会对性能造成影响,正好AntDesign的树(Tree)组件支持异步加载,于是我就想把异步加载封装为一个组件,可以减少接口数据返回,点击展开节点,动态加载数据。非常好用!二、前端实现需要接收一些......
  • 在树莓派上实现numpy的LSTM长短期记忆神经网络做图像分类,加载pytorch的模型参数,推理mn
    这几天又在玩树莓派,先是搞了个物联网,又在尝试在树莓派上搞一些简单的神经网络,这次搞得是LSTM识别mnist手写数字识别训练代码在电脑上,cpu就能训练,很快的:importtorchimporttorch.nnasnnimporttorchvisionimportnumpyasnpimportosfromPILimportImage#定义LSTM......
  • 2023-05-31 小程序本地图片加载不出来
    具体表现为:http://127.0.0.1:端口号/__pageframe__/uni_modules/static/logo.png原因:前端代码里对图片使用了../来引用路径,http://127.0.0.1:端口号识别不了该文件路径孤儿导致。解决方案:去除../引用,比如原来的../../../static/logo.png改为/static/logo.png。最后的话:en,不一定......
  • AdvancedInstaller制作Excel的COM加载项
       Excel控件安装之前,先检查一下以往的安装是否卸载干净,可以先文件-》加载项-》COM加载项里“删除”: 进入到AdvancedInstaller: Create后写入程序名:EXE方式:Add-In方式:一定要选编译器的Release版本: 默认全部选上,否则后期安装没有效果: 后续根据情况选版本:......