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

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

单例bean与类加载过程的更多相关文章

  1. Spring 源码学习 - 单例bean的实例化过程

    本文作者:geek,一个聪明好学的同事 1. 简介 开发中我们常用@Commpont,@Service,@Resource等注解或者配置xml去声明一个类,使其成为spring容器中的bean,以下我 ...

  2. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

  3. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

  4. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

  5. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  6. Spring源码分析:非懒加载的单例Bean初始化过程(下)

    上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...

  7. 【Spring源码分析】非懒加载的单例Bean初始化过程(上篇)

    代码入口 上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了f ...

  8. Spring源码分析:非懒加载的单例Bean初始化过程(上)

    上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了finish ...

  9. Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean

    Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean 七千字长文深刻解读,Spirng中是如何初始化单例bean的,和面试中最常问的Sprin ...

  10. 5.2:缓存中获取单例bean

    5.2  缓存中获取单例bean 介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了.前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例 ...

随机推荐

  1. 声网发布教育新品:灵动课堂全球覆盖学生超400万,互动白板首创H5课件

    3月31日,全球实时互动云服务商声网Agora在北京举行教育产品发布会,正式发布教育行业首款aPaaS产品"灵动课堂",和首个支持H5课件的白板PaaS产品"互动白板&q ...

  2. Java面试——Tomcat

    更多内容,前往个人博客 一.Tomcat 顶层架构 ​ Tomcat 中最顶层的容器是 Server,代表着整个服务器,从上图中可以看出,一个 Server可以包含至少一个 Service,用于具体提 ...

  3. vue使用keep-alive出现的的activated和deactivated生命周期

    <keep-alive>包裹的动态组件会被缓存,它是一个抽象组件,它自身不会渲染一个dom元素,也不会出现在父组件链中.当组件在 <keep-alive> 内被切换,它的 ac ...

  4. IOC创建对象方式

    IOC创建对象方式 User 类  public class User {  private String name;          public User(String name) {      ...

  5. 系统评价——层次分析法AHP的R语言实现(四)

    对一个事物的评价往往会涉及多个因素或者多个指标,评价是在多个因素相互作用下的一个综合判断.多指标综合评价方法具有以下的特点:包含若干个指标,分别说明被评价对象的不同方面,评价方法最终要对被评价对象作出 ...

  6. 技术分享:Proxy-Pool代理池搭建IP代理

    技术分享:Proxy-Pool代理池搭建IP代理 前言本章内容仅供参考,不涉及实际使用,主要使用Python环境和Redis数据库进行环境搭建,工具网盘存储如下,有问题可以私聊我.网址:https:/ ...

  7. [数据库/Linux]CentOS7安装MySQL Percona版(RPM方式)

    OS: CentOS7 (x86_64) MySQL: MySQL Percona 5.7.31-34 0 前置条件 已配置完成YUM源 已卸载先前可能安装的MySQL rpm -qa | grep ...

  8. day103:MoFang:用户登录部分:客户端提交登录信息&APICloud集成防水墙验证码&保存用户登录状态

    目录 bug:修复jsonrpc修改源码以后celery无法运行的问题 1.客户端提交登录信息 2.在APICloud中集成防水墙验证码 3.保存用户登录状态 bug:修复jsonrpc修改源码以后c ...

  9. devops|中小公司效率为王,没必要度量

    之前写过一篇文章<devops|中小公司不要做研发效能度量>,主要是从基础设施方向考虑,因为很多条件都不具备,贸然高投入去做研发效能度量可能达不到我们的预期效果,给出的建议是先做好当下打好 ...

  10. win系统安装wordcloud报错解决方案

    问题: ① 在命令行下执行pip install wordcloud出现报错如图: ②下载了错误的whl文件, 出现wordcloud-1.4.1-cp27-cp27m-win_amd64.whl i ...