单例模式:双重效验锁的懒汉实现方式

单例模式:双重效验锁的懒汉实现方式

单例模式

单例模式顾名思义就是指实例化一个对象,通过把构造方法私有化来禁止创建实例。

饿汉模式实现

饿汉模式的特点是在类加载的时候就创建并初始化一个实例,实例在整个程序运行期间都是唯一的

java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

这里的getInstance()必须是静态方法,因为静态方法可以通过类名.方法名的方式调用,而非静态方法则要创建实例,这与我们单例模式的规则不相符。

懒汉模式实现

懒汉模式的特点是需要的时候再创建实例,实例在整个程序运行期间都是唯一的。

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
           instance = new Singleton();
        }
        return instance;
    }
}

上面代码在单线程模式下是没问题的,但是在多线程模式下就会存在线程安全问题。

如果在首次创建实例,多个线程同时调用getInstance()方法,就有可能创建多个实例。

改进1 (创建多个实例)

我们可以进行对 getInstance() 方法进行加锁:

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

这样虽然解决刚刚线程安全的问题,但每次调用getInstance()方法都要加锁,增加不少的开销。

我们发现上面线程安全问题只存在于首次创建实例的情况,因此我们只需要对instance为空的时候单独处理即可。

改进2 (性能比较低)

instance为空的时候加锁,再判断一次是否为空即可:

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

粗略的看上去好像没什么问题,实际上这里还有一个指令重排序的问题

通过查阅资料知道instance = new Singleton();这个代码在执行的时候实际是执行分3步骤执行:

java 复制代码
memory = allocate(); //1.分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance=memory; //3. 设置instance指向刚分配的内存地址

JVM在执行的时候可能就会优化成 1->3->2的顺序执行

可能导致在多线程环境下,还没执行2就已经被其他线程返回了一个刚分配的地址

同样存在线程安全问题,需要使用volatile关键字来禁止指令重排序

改进3 (volatile禁止指令重排序)

java 复制代码
public class Singleton {
    //volatile 防止指令重排 和可见性
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
         //先判断对象是否已经实例化过,没有实例化才进入加锁代码
        if (null == instance) {
            //类对象加锁
            synchronized (Singleton.class) {
                //避免 singleTon== null时,第一个线程实例化后,进入阻塞状态的线程被唤醒后仍会进行实例化。
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

以上就是线程安全的懒汉模式。

相关推荐
LawrenceLan2 分钟前
Flutter 零基础入门(二十二):Text 文本组件与样式系统
开发语言·前端·flutter·dart
kylezhao20197 分钟前
C# 各种类型转换深入剖析
开发语言·c#
派大鑫wink8 分钟前
【Day39】Spring 核心注解:@Component、@Autowired、@Configuration 等
java·后端·spring
hxjhnct10 分钟前
JavaScript 的 new会发生什么
开发语言·javascript
少控科技14 分钟前
QT进阶日记004
开发语言·qt
阿杰 AJie18 分钟前
Lambda 表达式大全
开发语言·windows·python
格鸰爱童话19 分钟前
python基础总结
开发语言·python
输出输入24 分钟前
JAVA能进行鸿蒙系统应用的开发吗
java
a努力。24 分钟前
宇树Java面试被问:数据库死锁检测和自动回滚机制
java·数据库·elasticsearch·面试·职场和发展·rpc·jenkins
叁散29 分钟前
实验项目4 光电式传感器原理与应用(基于Matlab)
开发语言·matlab