创建型模式----单例模式

本文深入探讨了单例模式的设计理念,包括饿汉式与懒汉式的实现方式,并针对懒汉式单例模式中的线程安全问题,提供了基于volatile及类初始化的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 单例模式的概述

   确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例

2. 单例模式结构图

3. 饿汉式单例模式

代码实现

package com.zach.pattern.singleton;

/**
 * @Author:Zach
 * @Description:  饿汉式单例模式
 * @Date:Created in 10:57 2018/8/9
 * @Modified By:
 */
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton(){}  //private修饰防止在类的外部通过new实例化对象

    public static EagerSingleton getInstance(){
        return instance;
    }
}

当类被加载时,静态变量instance会被初始化,此时私有类的私有构造函数会被调用,创建唯一的实例

4. 懒汉式单例模式

懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这是延迟加载的技术,但是这并不是线程安全的

可通过DCL,即Double Check Lock,中卫双重检查锁定

package com.zach.pattern.singleton;

/**
 * @Author:Zach
 * @Description: 懒汉式单例模式
 * @Date:Created in 11:10 2018/8/9
 * @Modified By:
 */
public class LazySingleton {

    private static LazySingleton ls;

    private  LazySingleton(){}

    public static LazySingleton getInstance(){
        if(ls ==null) {
            synchronized (LazySingleton.class){
                if(ls == null){
                  ls = new LazySingleton();
                }
            }
        }
        return ls;
    }
}

上面的代码实现分析

   1)  如果第一个singleton不为null,则不需要执行下面的加锁动作

   2) 如果第一个singleton为null,即使有多个线程同一时间判断,但是由于synchronize存在,只有一个线程能够创建对象

   3) 当第一个获取锁的线程创建完singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象;

上面的代码看起来分析都没有错,但是通过查看资料发现,这也是错误的(有点震惊),先看看对象创建的过程

    1) 分配内存空间

     2) 初始化对象

     3) 将内存空间的地址赋值给对应的引用

由于重排序的缘故,步骤2,3可能会发生重排序,其过程如下: 

    1) 分配内存空间

     2) 将内存空间的地址赋值给对应的引用

     3)初始化对象

     如果2,3 发生重排序就会导致第二个判断出错,singleton !=null,但它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return 的singleton对象是一个没有被初始化的对象,所以DCL错误的根源在于:   ls = new lazySingleton();

5. 基于volatile的解决方案

package com.zach.pattern.singleton;

/**
 * @Author:Zach
 * @Description: 懒汉式单例模式
 * @Date:Created in 11:10 2018/8/9
 * @Modified By:
 */
public class LazySingleton {
    //通过volatile关键字来确保安全
    private volatile static LazySingleton ls;

    private  LazySingleton(){}

    public static LazySingleton getInstance(){
        if(ls ==null) {
            synchronized (LazySingleton.class){
                ls = new LazySingleton();
            }
        }
        return ls;
    }
}

    singleton声明为volatile后,步骤2与3就不会被重排序了,问题就解决

6. 基于类初始化的解决方案

    该解决方案的根本在于: 利用classloader的机制来保证初始化instance时只有一个线程,JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化

public class Singleton {
    private static class SingletonHolder {
        public static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance() {

        return SingletonHolder.singleton;
    }
}

  该解决方案的实质是允许步骤2,3重排序,但是不允许其他线程看见

Java语言规定,对于每一个类或者接口C,都有一个唯一的初始化锁LC与之相对应。从C到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化阶段期间会获取这个初始化锁,并且每一个线程至少获取一次锁来确保这个类已经被初始化过了

7. 参考资料

Java内存模型之从JMM角度分析DCL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zach_ZSZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值