Java 双重加锁单例与 java 内存重排序特性

版权声明:本文由吴仙杰创作整理,转载请注明出处:https://segmentfault.com/a/1190000009231182

1. 引言

在开始分析双重加锁单例代码之前,我们需要先理解 java 内存模式的重排序和无序写入特性。

2. Java 内存模型——重排序

在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽可能的开发并行度。

同样 Java 为了实现这一目标,在它的编译和处理时会对代码进行重新排序,从而达到更高的并行度提升程序性能。

Java 在进行重排序操作时会遵守数据依赖性,即编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

关于对于重排序的讲解,强烈推荐阅读程晓明写的《深入理解Java内存模型(二)——重排序》。

2.1 as-if-serial 语义

as-if-serial 语义
: 单线程下,为了优化可以对操作进行重排序

Java 编译器和处理器为单个线程实现了 as-if-serial 语义,但对于多线程并不实现 as-if-serial 语义。

2.2 无序写入

若程序定义的变量之间没有依赖关系,那么这两个变量在 JVM 中的加载顺序是不确定的。

3. 单例模式

单例模式带来的好处:

  • 方便共享通用的资源。

  • 避免频繁操作共享资源所带来的性能消耗。

而我们已单例模式有有饿汉式(static 变量,在类加载时就进行初始化一次)懒汉式(在使用到时才初始化一次)两种,考虑到对于始初化单例类的开销较大,往往我们需要创建单例是懒加载的,即在程序使用到单例时才创建,从而可以避免创建单例时拖慢程序的启动速度。

所以对于使用单例模式有两个要求:(1)懒加载。(2)多线程安全

3.1 双重加载的单例模式分析

DCL(double-checked locking) 即双重检查加锁。因为 DCL 模式的单例是懒加载的,所以这往往也是在许多项目中最容易见到的单例模式写法。但是这种方式创建的单例,是多线程安全的吗?

对于双重检查加载的单例代码:

package com.wuxianjiezh.demo.threadpool;

public class Singleton {

    private static Singleton instance;

    // 私有化的构造方法,保证外部的类不能通过构造器来实例化
    private Singleton() {
    }

    // 双重检查加锁来获取对象单例
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

假设有二个线程要获取上面的单例,当其中 线程一 进入同步块执行到 instance = new Singleton(); 时,线程二 来到了 外的第一个 null 判断。注意这里,线程一 在执行 instance = new Singleton(); 这段代码时有以下几个步骤,其中执行是无序的(无序写入),可能出现下面这种情况:

// 1. 为 Singleton 对象分配内存
memory = allocate();
// 2. 注意现在 instance 是非空的,但还没初始化
instance = memory;
// 3. 调用 Singleton 的构造函数,传递 instance
ctorSingleton(instance);

当在执行到 instance = memory; 时,线程二 进入了第一次的 null 判断,此才 线程二 判断 instance 不为 null,返回了 instance,但此时返回的不是单例的实例对象,而是内存对象。

3.2 单例模式推荐写法

使用静态内部类:

package com.wuxianjiezh.demo.threadpool;

public class Singleton {

    // 私有化的构造方法,保证外部的类不能通过构造器来实例化
    private Singleton() {
    }

    // 静态内部类只会被加载一次
    // 内部类 SingletonHolder 只有在 getInstance() 方法第一次调用的时候才会被加载(实现了lazy)
    // 而且其加载过程是线程安全的(多线程安全)
    private static class SingletonHolder {
        // 单例变量

        // 常规写法
        // private static final Singleton instance = new Singleton();
        
        // 假设单例对象构造方法会抛出异常时的写法
        private static final Singleton instance;

        static {
            instance = new Singleton();
        }
    }

    // 获取单例对象实例
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值