单例模式的实现

一、综述

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

单例模式分为两种: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;
   }
}
相关推荐
Mahir0811 小时前
Spring 循环依赖深度解密:从问题本质到三级缓存源码级解析
java·后端·spring·缓存·面试·循环依赖·三级缓存
杜子不疼.11 小时前
【C++ AI 大模型接入 SDK】 - DeepSeek 模型接入(上)
开发语言·c++·chatgpt
加号311 小时前
【C#】 串口通信技术深度解析及实现
开发语言·c#
sycmancia12 小时前
Qt——编辑交互功能的实现
开发语言·qt
RyFit12 小时前
SpringAI 常见问题及解决方案大全
java·ai
石山代码12 小时前
C++ 内存分区 堆区
java·开发语言·c++
绝知此事12 小时前
【算法突围 01】线性结构与哈希表:后端开发的收纳术
java·数据结构·算法·面试·jdk·散列表
无风听海12 小时前
C# 隐式转换深度解析
java·开发语言·c#
一只大袋鼠13 小时前
Git 进阶(二):分支管理、暂存栈、远程仓库与多人协作
java·开发语言·git
LuminousCPP14 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习