DCL双重检查

本文探讨了单例模式实现中的并发问题,如何通过volatile关键字防止指令重排序,以及在不同场景下选择适当的同步策略以平衡性能与线程安全性。重点介绍了使用synchronized和volatile关键字的解决方案。
摘要由CSDN通过智能技术生成

第一次写单例模式

public class Singleton {
	private static Singleton instance = null;
	public static Singleton getInstance() {
		if(instance == null) {//1
			instance = new Singleton();//2
		}
		return instance;
	}
}

假设现在有两个线程,两个线程都调用getInstance()方法,假设线程A执行完1还没有执行2,此时cpu资源切换给线程B,线程B执行1发现instance为空,执行完new Singleton(),稍后线程A被唤醒,执行new Singleton(),问题出现了,new出了两个实例

将代码改为如下所示

public class Singleton {
	private static Singleton instance = null;
	public synchronized static Singleton getInstance() {
		if(instance == null) {//1
			instance = new Singleton();//2
		}
		return instance;
	}
}

这样可以保证线程不出现问题了,但是问题又来了,第一次调用getInstance()初始化instance后每次getInstance()都要进同步方法,性能较为低下,耗时操作都浪费再同步方法上了

正确的做法是只第一次调用getInstance()时进同步方法,往后再调用就直接返回,代码如下

public class Singleton {
	private static Singleton instance = null;
	public static Singleton getInstance() {
		if(instance == null) {
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

instance = new Singleton()这里大致做了三件事情

  1. 给instance实例分配内存
  2. 初始化instance的构造器
  3. 将instance对象指向分配的内存空间(注意到这步时instance就非null了)

但JVM为了优化指令,提高程序运行效率,允许指令重排序,因此在程序真正运行时以上指令执行顺序可能是这样的

  1. 给instance实例分配内存
  2. 将instance对象指向分配的内存空间(注意到这步时instance就非null了)
  3. 初始化instance的构造器

这是如果线程A执行到第二步切换到线程B,线程B发现instance不为空,直接取走使用,就报错了

为了解决以上问题,我们可以使用volatile关键字,禁止指令重排序

public class Singleton {
	private volatile static Singleton instance = null;
	public static Singleton getInstance() {
		if(instance == null) {
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值