由浅入深单例详解

本文详细探讨了单例模式的两种实现方式:懒汉模式与饿汉模式。懒汉模式在多线程环境下可能造成单例对象的重复构建,通过双重检测机制与volatile关键字解决线程安全问题。饿汉模式则在类加载时即构建单例,避免了线程安全问题。最后介绍了静态内部类优雅单例模式,结合了懒汉模式的内存优化与饿汉模式的安全性。

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

一.单例模式第一版:懒汉模式

public class Singleton {
    private Singleton(){}//私有构造函数
    private static Singleton instance=null;//单例对象
    public static Singleton getInstance(){
    //静态工厂方法
        if (instance==null){
            instance=new Singleton();
        }

        return instance;
    }
}

如果单例初始值是null,还未构建,则构建单例对象并返回,这种写法属于懒汉模式。

如果单例对象一开始就被new Singleton()主动构建,则不需要判空操作,这种写法属于饿汉模式。

众所周知,懒汉模式是非线性安全的,饿汉模式是线性安全的。那么为什么说懒汉是不安全的呢?

(什么是线程安全呢?jvm有一个main memory,线程有自己的working memory,一个线程对一个变量进行操作的时候,都要在自己的working memory里建立一个copy,操作完之后存入main memory.多个线程同时操作同一个变量,就可能会出现不可预知的结果。线程安全就是说多线程访问同一代码,不会产生不确定的结果。)

假设,当Singleton类刚被初始化,instance对象还是null,这时候有两个线程同时访问getInstance(),

因为instance=null,所以两个线程都通过了if条件判断,开始执行new Singleton(),结果就是instance被构建了两次,结果充满了不确定性,不能保证是唯一单例。

二.饿汉模式优化方案:

public class Singleton {
    private Singleton(){}//私有构造函数
    private static Singleton instance=null; //单例对象
    public static Singleton getInstance(){
        if (instance==null){//双重检测机制
            synchronized (Singleton.class){//同步锁
                if (instance==null){//双重检测机制
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
}

1.为了防止new Singleton()被多次执行,因此在new之前使用Synchronized同步锁,锁住整个类

2.当两个线程同时访问,进入Synchronized内之后,如果线程A构建玩对象,也就是已经执行完 instance=new Singleton();的时候,线程B刚好先一步通过了第一次判空验证,如果不做第二次判空的话,线程B还是会再次构建instance

经过上面的优化,表面看起来已经没有问题了。但是由于JVM编译器的指令重排机制,所以上述代码还存在一个漏洞。

指令重排是什么意思呢?比如instance = new Singleton,会被编译器编译成如下指令:

memory=allocate();//分配对象内存空间

ctorInstance(memory);//初始化对象 

instance=memory;//设置instance指向刚设置的地址

这个指令顺序不是一成不变的,有可能经过jvm和cpu的优化指令重拍成下面顺序:

memory =allocate();    //1:分配对象的内存空间 

instance =memory;     //3:设置instance指向刚分配的内存地址 

ctorInstance(memory);  //2:初始化对象 

当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行  if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。为了解决这个问题我们可以这样做,

 private volatile static Singleton instance = null;  //单例对象

经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:

 

memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置instance指向刚分配的内存地址 

静态内部类优雅单例模式
由于静态内部类只加载一次,所以可以用这个特性来实现单例,这个方式比(double check+volatile)更优雅

public class SingleInstance {

     public static SingleInstance getInstance(){

            return InnerClass.singleInstance;

     }

    private static class InnerClass{

           private static SingleInstance singleInstance = new SingleInstance()

   }

}

优势:

  • 兼顾了懒汉模式的内存优化以及饿汉模式的安全性。

  劣势:

  • 需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。
  • 创建的单例,一旦在后期被销毁,不能重新创建。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值