单例模式的实现

一、综述

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

单例模式分为两种: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;
   }
}
相关推荐
木风小助理2 小时前
QPS监控:SpringBoot应用性能监控的必要性与实践
java·spring boot·后端
strive-debug2 小时前
cpp篇~~类和对象
开发语言·c++
是宇写的啊2 小时前
单例模式-阻塞队列
java·开发语言·单例模式
进击的小头2 小时前
Git在C项目中的分支策略和规范
c语言·开发语言·git
u0104058362 小时前
Java中的单例模式详解
java·开发语言·单例模式
霸道流氓气质2 小时前
SpringBoot+modbus4j实现ModebusTCP通讯定时读取多个plc设备数并存储进redis中
java·spring boot·redis·modbustcp·plc
Allen_LVyingbo2 小时前
构建医疗AI数据集建设平台:Go语言工程方案详解
开发语言·人工智能·自然语言处理·golang·知识图谱·健康医疗
历程里程碑2 小时前
哈希1:两数之和:哈希表优化指南
java·开发语言·数据结构·c++·算法·哈希算法·散列表
XerCis2 小时前
Python包与环境管理工具uv及pyproject.toml指南
开发语言·python·uv