前言:
眼前同学们对“单例模式线程安全问题”大致比较着重,你们都需要剖析一些“单例模式线程安全问题”的相关资讯。那么小编也在网上搜集了一些对于“单例模式线程安全问题””的相关知识,希望大家能喜欢,朋友们一起来学习一下吧!单例模式,即我们只允许一个类有且仅有一个实例对外提供服务。通常为了性能考虑,单例模式会以懒加载的形式创建,也即单例中的懒汉模式,与之相对的当然就是单例的饿汉模式:不管用不用,我可以先给你创建出来。
1.单线程下的单例模式实现
非多线程环境下,由于不存在线程对共享数据(对象)的竞争,所以也就没有线程安全问题,其单例模式实现如下:
public final class FooSingleton { //持有类的唯一实例 private static FooSingleton instance = null; /** * 私有构造函数 */ private FooSingleton() { //do nothing } /** * 提供外部获取实例的工厂方法 * 非线程安全 * @return */ private static FooSingleton getInstance(){ if (null == instance) {//非线程安全 instance = new FooSingleton(); } return instance; } public void methodOther() { //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法 }}2.多线程下的单例模式实现
可以发现,在多线程环境下,这个获取单例的方法不是线程安全的:
private static FooSingleton getInstance(){ if (null == instance) {//非线程安全 instance = new FooSingleton(); } return instance; }
这就会导致客户端(也就是各个调用端)获取到的对象并不是同一个对象,这显然就违背了我们使用单例模式的初衷了。怎样保证线程安全呢?常用的实现方法有如下三种:
2-1 直接加锁实现线程安全的单例模式
直接加锁实现线程安全的单例模式:
public final class FooSingletonSafety { //持有类的唯一实例 private static FooSingletonSafety instance = null; /** * 私有构造函数 */ private FooSingletonSafety() { //do nothing } /** * 提供外部获取实例的工厂方法 * 线程安全:加锁实现 * synchronized保障了原子性、可见性和有序性 * @return */ private static FooSingletonSafety getInstance(){ synchronized (FooSingletonSafety.class) { if (null == instance) { instance = new FooSingletonSafety(); } } return instance; } public void methodOther() { //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法 }}
显然,直接加锁是可以保证单例模式的线程安全的,但是你可能会发现,这种实现方式下,每个线程在获取单例对象的时候都要去抢锁,获取之后再释放锁,这显然效率是不高的(每次获取对象都要有锁开销)。所以,一般我们会通过下面的DCL去实现线程安全的单例模式
2-2 DCL实现线程安全的单例模式
DCL,即double-check-locking双重检查锁定,代码实现如下:
public final class FooSingletonBasedDCL { //持有类的唯一实例 private static volatile FooSingletonBasedDCL instance = null; /** * 私有构造函数 */ private FooSingletonBasedDCL() { //do nothing } /** * 提供外部获取实例的工厂方法 * 线程安全:DCL实现 * synchronized保障了原子性、可见性和有序性 * volatile保证校验时(null == instance) instance对各个线程是可见的,同时禁止JIT和处理器重排序 * @return */ private static FooSingletonBasedDCL getInstance(){ if (null == instance) { synchronized (FooSingletonSafety.class) { if (null == instance) { //new关键字对应三条指令:1.分配对象所需的存储空间 2.实例化对象 3.将对象引用赋值给变量 //这三条指令正常执行顺序是1->2->3,但是JIT编译器或处理器在执行时可能会将其优化为1->3->2 //所以,为了保证instance的可见性以及禁止重排序,所以instance变量必须要用volatile修饰 //否则,极端情况下,一个线程在判断if (null == instance)时,instance不为空直接返回,但此时 //instance可能是个半对象(对象并没有经过构造方法初始化),这将会造成代码错误 instance = new FooSingletonBasedDCL(); } } } return instance; } public void methodOther() { //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法 }}
DCL你可能开发中基本没用过,但是这种实现方式在很多开源框架如Spring、dubbo等中都有使用
2-3 利用类加载机制实现线程安全的单例模式
利用Java的类加载机制:加载->链接->初始化,我们可以轻易的实现线程安全的单例模式,这种单例模式形式最简单,代码实现如下:
原理:Java类的初始化只会发生一次(同一个类加载器下)
public final class BarSingleton { //利用类初始化只会发生一次,创建线程安全的单例 private static final BarSingleton INSTANCE = new BarSingleton(); /** * 私有构造函数 */ private BarSingleton() { //do nothing } public static BarSingleton getInstance() { return INSTANCE; } public void methodOther() { //类提供的实例方法,调用方获取单例对象后,可调用类中的实例方法 }}
标签: #单例模式线程安全问题