单例模式的实现

一、综述

单例模式就是一个类只能有一个实例对象。

单例模式分为两种:1,懒汉式单例 2,饿汉式单例 他们又分别有传统实现和优化的推荐实现

二、懒汉式单例:当需要使用对象的时候才进行实例化

由于可能有多个线程同时要使用对象,因此需要考虑线程安全问题,防止并发访问时生成多个实例。通常需要加锁来解决并发冲突,是用时间换空间的方案。

1.传统的写法

java 复制代码
class  Singleton{
        //设置私有构造方法
          private Singleton(){}
        //声明一个Singleton对象为obj
         private static Singleton obj; 
        //加锁保证obj只能实例化一次,时间换空间
        public static synchronized  Singleton getInstance(){

                if(obj == null){
                    obj=new Singleton();   
                }
           return    obj;
        }
} 

传统实现方式中,每次获取实例都要被synchronized关键字串行化,即使已经生成了对象实例。

而我们加锁的目的是为了防止生成多个实例,因此其实只需要对生成实例的代码加锁,生成实例后,可支持并发访问,提高性能。

2.优化实现代码:双检法

java 复制代码
class Singleton{
        //设置私有构造方法
          private Singleton(){}
        // 最后解释volatile关键字
           private  volatile static Singleton  obj;
        //获取实例对象的方法
           public static Singleton  getInstance(){
              //如果已有实例则直接返回,不走锁,避免无意义加锁
               if(obj==null){
                  //仅在没生成实例时加锁控制,使并发访问串行化
                   synchronized(Singleton.class){
                          //多个线程会按序执行到此处,需要再次检查是否已经实例化
                             if(obj==null){
                                obj = new Singleton();
                      }
                   }
               }
             return  obj;
     } 
}

线程 A:通过外层if → 拿到锁 → 执行内层if(obj 为 null)→ 创建实例 → 释放锁。

线程 B:之前在锁外等待 → 线程 A 释放锁后,线程 B 拿到锁 → 执行内层if(此时 obj 已经被线程 A 创建,不为 null)→ 直接退出同步块,不会重复创建实例。

2.1为什么使用volatile关键字

因为使用new来创建对象不是一个原子操作(不可分割的操作序列,要么都成功,要么都失败),而是会被编译成如下三条指令:

  1. 给实例分配内存
  2. 初始化实例的构造
  3. 将实际对象指向分配的内存空间(此时实例应该已经不为空)
    正常的思路是123一定按顺序执行。
    但事实上,Java会对进行指令重排序。
    即JVM虚拟机在执行上面三条指令时,可能按照132的顺序执行。

假设当13执行完,2还未执行时,如果另外一个线程调用getInstance(),会在判断对象是否为null时返回false(因为3已执行,对象指向了内存空间,已不为空),然后直接返回实例。但由于此时2还没执行,实例并未完全初始化,只是分配了内存空间,就会导致使用对象时出现错误(引用逃逸)。

而voliate关键字可以通过内存屏障禁止指令重排序,保证创建对象时的123步骤按顺序执行,从而解决上述问题。

三、饿汉式单例

1.传统实现

通过static关键字,在类加载时创建对象。

java 复制代码
Class Singleton{
       //私有构造方法
     private Singleton(){ };
       //类加载时就实例化对象 加static
     private static Singleton obj=new Singleton();

    public static Singleton getInstance(){
         return  obj;
    } 
}     

2.优化实现

由于类加载时就实例化对象,因此当我们调用这个类的其它静态方法时,可能并不需要实例对象,也会触发类加载,从而实例化单例独享,会导致空间的暂时浪费。

由于静态内部类中的对象不会默认加载 ,直到调用了获取该内部类属性的方法。因此可用静态内部类封装静态实例变量。

java 复制代码
class Singleton{
// 私有构造函数
private Singleton() {}

// 静态内部类
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
     return SingletonHolder.instance;
   }
}
相关推荐
银河技术14 分钟前
从脚本到 Operator:用 Go 构建可靠的微服务自动扩缩容控制器
开发语言·微服务·golang
GIS数据转换器2 小时前
地下空间综合管廊运营管理平台
大数据·开发语言·人工智能·经验分享·perl
猿究院_夏燊7 小时前
Spring Bean 生命周期
java·后端·spring
Wang's Blog7 小时前
Java 项目实战: 交友项目:登录链路回顾与今日任务地图
java·开发语言·状态模式·交友
信誓旦旦的程序猿8 小时前
【Python量化实战宝典 #08】可转债与债券接口手册:转股价·溢价率·纯债价值(Python 可跑)
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
砚底藏山河8 小时前
python量化:行情数据回溯
java·开发语言·python·金融·maven
今年下半年8 小时前
AI智能体项目实战记录(02):初始化 Python 项目
开发语言·人工智能·python·fastapi
Jlzn88888 小时前
华东电芯分选通道数怎么配才不堵线
java·人工智能·架构·自动化·制造
Williamtym8 小时前
PickGo Lab实践:用浏览器、Python和MCP串起机器人搬运仿真
开发语言·python·机器人
香吧香8 小时前
GRPC连接机制---长连接分析
java·网络与传输协议