Java单例设计模式(懒汉式:DoubleCheck+volatile)

单例设计模式

即某个类在整个系统只能有一个实例对象可被获取和使用的代码模式;例如代表JVM运行环境的Runtime类

单例模式要素

  1. 私有静态引用指向自己的实例
  2. 私有的构造方法
  3. 返回实例对象的公有静态方法

饿汉式单例模式:直接创建对象,不存在线程安全问题
懒汉式单例模式:延迟创建对象,需要时再创建,节省空间

懒汉式:DoubleCheck+volatile

public class Singleton {
    // 1.私有静态引用指向自己的实例
	private static volatile Singleton singleton;
	// 2.私有的构造方法
	private Singleton() {}
	// 3.返回实例对象的公有静态方法
	private static Singleton getInstance() {
		if (singleton == null) {
			synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}

执行过程

// 1.调用Singleton的getInstance静态方法
Singleton singleton = Singleton.getInstance();
// 2.创建实例对象
singleton = new Singleton();
// 3.返回实例对象
return singleton;
// 此后,每次调用getInstance方法都直接返回该实例对象

类加载时,初始化静态成员变量singleton1
调用getInstance静态方法,在栈中创建引用变量singleton2
同时,第一次getInstance方法会执行new Singleton(),在堆中创建一个实例对象singleton3

三者关系
在这里插入图片描述
堆区中只会存在一个singleton实例对象,此后每次调用getInstance方法实际上都是返回该对象。

DoubleCheck的作用

第一次:判断singleton是否已经实例化,如果是,直接返回,否则,执行实例化。
第二次:对Singleton.class加锁后,再次判断singleton是否实例化,因为在并发场景下,该线程在判断singleton==null之后和加锁之前,其他线程已经实例化,所以需要再次判断。

Java对象实例化

  1. 确认类元信息是否存在;即在JVM内存的元数据区检查需要创建的类元信息是否存在,若不存在,则在双亲委派模式下查找对应的.class文件。如果没有找到文件,抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class类对象。
  2. 分配对象内存;计算对象占用空间大小,如果实例成员变量是引用变量,仅需要分配4个字节的引用变量空间,接着在堆中划分一块内存给新对象。
  3. 设定默认值;即为成员变量设定各种形式的零值。
  4. 设置对象头;设置新对象的哈希码、GC信息、锁信息、对象所属的类元信息等。
  5. 执行init方法;初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

volatile的作用

volatile解决多线程共享变量的可见性问题。

public class Singleton {
    // 1.私有静态引用指向自己的实例
	private static volatile Singleton singleton;
	// 2.私有的构造方法
	private Singleton() {}
	// 3.返回实例对象的公有静态方法
	private static Singleton getInstance() {
		if (singleton == null) {
			synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}

如果不使用volatile关键字,使用方在调用getInstance方法时,有可能会得到未初始化完成的对象。
因为对java编译器而言,初始化singleton实例和将对象地址写入singleton字段非原子操作,且两个阶段的执行顺序是未定义的。

(1)初始化singleton实例
(2)静态成员变量singleton指向该地址,即将堆地址赋值给singleton字段

假设某线程执行new singleton()时,先执行(2),此时,另一个线程调用getInstance方法,singleton!=null,会直接返回一个不正确的singleton单例对象,因为此时(1)还没有执行成功,即对象里面的成员变量没有完成赋值,还是默认值。
volatile关键字修饰目标属性,这样singleton限制了编译器对它的读写操作进行指令重排,确定对象实例化后才返回引用。

字节码分析

/* new determines the size in bytes of instances of the given class and allocates memory 
the new instance from the garbage collected heap. The fields of the instance are set to the
initial value 0, or null. Next, a reference to the new object is pushed onto the operand
stack 
*/
NEW designModel/Singleton
/* This pops the top single-word value off the operand stack, and then pushes that value 
twice
*/
DUP
// The main use of invokespecial is to invoke an object's instance initialization method
INVOKESPECIAL designModel/Singleton.<init>()V
// Pops objectref off the stack and stores it in local variable
ASTORE 1
  1. 为对象分配内存空间,初始化成员变量值为0或者NULL;将对象堆首地址赋值给引用变量,压栈;
  2. 复制栈顶地址,即先pop一次,然后再push两次,对于此步操作的意义,网上的解释是:一般实例化一个对象后紧接着会使用它,将引用变量存回局部变量表的同时保留一份在栈上;
  3. 调用对象的实例初始化方法,即为对象的成员变量赋值;
  4. 将对象引用从栈中取出,存入局部变量表。

如果不使用volatile关键字,步骤1中的”将对象堆首地址赋值给引用变量“和步骤3中的“为对象的成员变量赋值”可能指令重排,无法保证返回正确的对象。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值