单例模式写法

单例模式确保一个类只有一个实例,并提供全局访问点。文章介绍了饿汉模式(类加载时初始化实例)和懒汉模式(按需创建实例),以及在多线程环境下懒汉模式的安全问题。为解决这个问题,使用了`synchronized`和`volatile`关键字来保证线程安全和内存可见性。
摘要由CSDN通过智能技术生成

单例模式的写法

什么是单例模式 ?

单例模式数以Java 中的设计模式之一, 这种模式设计到一个单一的类, 该类负责创建自己的对象, 并且保证只有单个对象被实例化 , 并且该类只提供一种获得该类实例的方法,(构造设置为私有)

  • 该单例类只允许存在一个类实例,
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一事例

为什么要设计单例模式?

如果一个类的实际大小由100G(假设很大) 但是内存只有200G 此时 我们必须保证该类只能实例化一个对象, 如果程序员不小心多创建了一个该类, 那么此时内存就会被占满, 从而出现问题. 因此我们设计出单例模式来避免该问题, 提供一个唯一的获取实例的方法,这样就不会出现创建出两个实例的情况了.

饿汉模式

单例模式的第一种方法, 也就是当创建一个对象的时候, 直接初始化该对象的构造(对唯一的实例的初始化比较着急,类加载的时候会直接创建实例)

class Singleton {
    //也就是在一个Java程序中,一个类对象只存在一份(JVM保证的)
    //进一步也就保证了类的static成员也是只有一份 唯一的.
    //1. 使用static 创建一个实例 并且立即实例化.
    // 这个instance 对应的实例, 就是该类的唯一实例.
    private static Singleton instance = new Singleton();
    //2. 为了防止程序员不小心在其他地方new了一个对象 因此 构造方法创private
    private Singleton(){}
    //3. 提供一个方法 让外面能拿到唯一实例
    public static Singleton getInstance(){
        return instance;
    }
}

外面拿到该实例只能通过 调用 getInstance 方法 来获取该对象的实例

public static void main(String[] args) {
    //保证有唯一实例 对象不能new出来
    Singleton singleton = Singleton.getInstance();
}

懒汉模式

单例模式的第二种方法,在外部调getInstance 方法的时候在开始对唯一的实例进行初始化,

好处是: 节省了整体的资源

坏处是: 需要考虑在多线程的情况, 创建实例是否安全

class Singleton2{
    //1.就不是立即初始化实例
    private static Singleton2 instance = null;
    //2.把构造方法设为private
    private Singleton2 (){}
    //3.提供一个方法来获取到上述单例的实例
    // 只有当真正需要用这个实例的时候,才会真正的去创建这个实例.
    public static Singleton2 getInstance(){
        if (instance == null) {
            return new Singleton2();
        }
        return instance;
    }
}
public static void main(String[] args) {
    //保证有唯一实例 对象不能new出来
    Singleton2 singleton = Singleton2.getInstance();
}

懒汉模式在多线程中的安全问题(重点)

在多线程中, 因为线程的调度是随机的, 因此可能会多个线程同时调用 getInstance 方法, 如果不进行处理, 可能就会出现该类被多个线程创建了实例,此时该模式就不能成为单例模式了.

因此我们需要对instance方法进行加锁, 保证该方法的原子性

这样就可以保证if内的两个方向 (一个判断成功创建实例,一个判断失败返回实例) 成为原子性.

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

仔细看这个代码 我们就会产生出问题, 单例模式的实例的创建只会创建一次, 在实例创建完成之后, 每个线程都会继续进行判断if 可是此时明明实例以及被创建 , 剩下的只是读取操作 而不是修改操作, 我们之前说的多个线程读取同一份变量的时候不会引发线程安全的问题. 因此 我们可以在synchronized 外面再加一层判断, 如果说 实例已经被创建 那么我们就不会进行判断 ,加锁, 从而降低效率 , 而是直接进行返回实例操作. 并对变量加入volatile 关键字 保证内存可见性.

class Singleton2{
    //1.就不是立即初始化实例
    //增加 volatile 保证内存可见性
    private static volatile Singleton2 instance = null;
    //2.把构造方法设为private
    private Singleton2 (){}
    //3.提供一个方法来获取到上述单例的实例
    // 只有当真正需要用这个实例的时候,才会真正的去创建这个实例.
    public static Singleton2 getInstance(){
        if (instance == null) {
            //未初始化过的单例的才会出现线程安全问题.
            synchronized (Singleton2.class){
                if (instance == null) {
                    return new Singleton2();
                }
            }
        }
        return instance;
    }
}

当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作.
当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例

此时又有读者会有疑问了,如果此时我们直接在第一个if内直接进行加锁 ,返回实例不就可以了?

即:

if (instance == null) {
    synchronized (Singleton2.class) {
     	return new Singleton2();   
    }
} 
return instance;

答案是不可以的, 因为之间我们说了, if 判断 是分为两个操作的, 因此,为了保证原子性, 需要将内层的if 进行包裹, 因为两个if 的作用是完全不一样的.

总结:

  1. 需要加两层if ,第一层是判断线程是否已经实例完对象, 如果完成就不需要进行锁竞争, 直接获取instance对象,

    第二层是当线程还没有创建实例, 防止多个线程进行创建实例,而导致实例被创建了多份,

  2. 需要加volatile关键字, 如果实例创建完成, 此时只剩下读取操作, 不会再修改instance 的值, 为了保证内存可见性, 加上volatile 关键字.

  3. 加synchronized关键字, 保证内层if 语句的原子性, 就不会再出现实例被创建了多份.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值