单例bean与类加载过程

这篇具有很好参考价值的文章主要介绍了单例bean与类加载过程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

构造单例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

这时为什么呢?

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

单例bean与类加载过程

而在准备阶段将会:

  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数据不一致的问题了!

谢谢大家阅读,才疏学浅,望多多指教!文章来源地址https://www.toymoban.com/news/detail-465829.html

到了这里,关于单例bean与类加载过程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring的加载配置文件、容器和获取bean的方式

    🐌个人主页: 🐌 叶落闲庭 💨我的专栏:💨 c语言 数据结构 javaweb 石可破也,而不可夺坚;丹可磨也,而不可夺赤。 properties文件: jdbc.properties 1.开启context命名空间 2.使用context命名空间,加载指定properties文件 3.使用属性占位符 ${} 读取properties文件中的属性 properties文件

    2024年02月15日
    浏览(35)
  • 〖大前端 - 基础入门三大核心之JS篇(53)〗- 构造函数与类

    说明:该文属于 大前端全栈架构白宝书专栏, 目前阶段免费 , 如需要项目实战或者是体系化资源,文末名片加V! 作者:哈哥撩编程,十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司担任研发部门CTO。 荣誉: 2022年度博客之星Top4、2023年度超级个体得主、谷

    2024年02月02日
    浏览(59)
  • 实战讲解及分析Spring新建Bean的几种方式以及创建过程(图+文+源码)

    作为一个应用开发人员而言,会使用某一个工具分为两个层次(个人观点): 第一个层次,知道工具,会使用这个工具解决问题; 第二个层次,理解工具的实现原理。 关于Spring的学习,还在第一个层次转悠,缺少原理的研究, 随着学习的深入,开始研究些Spring源码,配合

    2023年04月08日
    浏览(38)
  • 113、单例Bean是单例模式吗?

    通常来说,单例模式是指在一个JVM中,一个类只能构造出来一个对象,有很多方法来实现单例模式,比如懒汉模式,但是我们通常讲的单例模式有一个前提条件就是规定在一个JVM中,那如果要在两个JVM中保证单例呢?那可能就要用分布式锁这些技术,这里的重点是,我们在讨

    2024年02月14日
    浏览(48)
  • 如何构造一个安全的单例?

    我们知道,单例是一种很常用的设计模式,主要作用就是节省系统资源,让对象在服务器中只有一份。但是实际开发中可能有很多人压根没有写过单例这种模式,只是看过或者为了面试去写写demo熟悉一下。那为啥说是一种常用的模式? 其实我们用的spring管理对象生命周期,

    2024年02月13日
    浏览(40)
  • 构造函数注入指定bean名称

    如图执行结果,通过@Qualifier指定调用syncScheduler2线程池100个

    2024年02月09日
    浏览(44)
  • 单例模式与构造器模式

    单例模式(Singleton Pattern):创建型模式,提供了一种创建对象的最佳方式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建 在应用程序运行期间,单例模式只会在全局作用域下创建一次实例对象,让所有需要调用的地方都共享这一单

    2024年02月15日
    浏览(37)
  • C++类的多种构造函数

    下面以Complex 复数类来学习C++类中的各种构造函数; 未显式提供初始值时,用来创建对象的构造函数; 如果该类采用了继承或者定义了虚函数或者成员由非内置类型,那么系统在我们没有定制写出该函数的时候,会自动生成默认构造函数; 用户可以传参与对应普通构造函数进行

    2024年01月18日
    浏览(34)
  • Spring中Bean的生命周期以及Bean的单例与多例模式

    bean的生命周期可以表达为:bean的定义➡bean的初始化➡bean的使用➡bean的销毁 1)通过XML、Java annotation(注解)以及Java Configuration(配置类) 等方式加载Bean 2)BeanDefinitionReader:解析Bean的定义。在Spring容器启动过程中, 会将Bean解析成Spring内部的BeanDefinition结构; 理解为:将

    2024年02月12日
    浏览(35)
  • Spring -- 单例Bean是线程安全的吗?

    Spring默认情况下就是单例的 但是可以设置 @Scope 的值为 prototype 将Bean设置为多例的,如下 那么单例Bean是线程安全的吗? 不是 但是 看以下代码 有个成员变量 count ,成员变量是 需要考虑线程安全问题的 userService 是无状态的, 因此无需考虑线程安全问题 getById中的参数id是形

    2024年02月13日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包