懒汉式单例的3个坑

单例模式

Singleton

单例模式是创建型模式,通过单例模式创建的类只有一个实例存在

有时候完全只需要一个实例存在就够了,不用那么多实例存在

单例模式使用场景

  1. 频繁创建、销毁对象
  2. 创建对象耗时耗资源
  3. 工具类

懒汉式单例

懒汉式单例可以理解成懒加载,要使用时再加载

通常需要先私有化构造,通过方法获取时需要判断单例是否已经生成

java 复制代码
//懒汉式 
public class SingLeton02 {
    private SingLeton02(){

    }
    //类加载时 不初始化 所以不加final
    private static SingLeton02 INSTANCE;

    public static SingLeton02 getInstance(){
        //第一次调用这个方法时,INSTANCE为空实例化对象,之后不为空了就直接返回这个单例
        if (INSTANCE == null){
             /*try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            INSTANCE = new SingLeton02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        //多线程测试 发现线程不安全(测不出来可以在getInstance方法中线程睡眠) 
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(SingLeton02.getInstance())).start();
        }
    }
}

线程不安全原因:当线程A进入getInstance方法判断INSTANCE为空,此时线程B也进入getInstance方法并判断INSTANCE为空,然后就造成了实例多个对象,并非单例

懒汉式加锁优化

既然懒汉式线程不安全,那就加锁

java 复制代码
//懒汉式加锁优化
public class SingLeton03 {
    private SingLeton03(){

    }

    private static SingLeton03 INSTANCE;

    //因为这里是同步方法,所以锁的是SingLeton03这个Class对象
    public synchronized static SingLeton03 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new SingLeton03();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(SingLeton03.getInstance())).start();
        }
    }
}

虽然线程安全了,但是因为加了synchronized效率变低了(每次操作都要看是否加了锁等....)

懒汉式双重检测锁DCL

多线程安全

将同步方法变为同步块再优化

java 复制代码
//懒汉式双重锁优化
public class SingLeton04 {
    private SingLeton04(){

    }
	//volatile防止指令重排序
    private volatile static SingLeton04 INSTANCE;

    public  static SingLeton04 getInstance(){
        //这里的判断条件很有必要,如果没有这条判断条件,线程一进来直接抢锁降低效率
        if (INSTANCE == null){
            synchronized (SingLeton04.class){
                if (INSTANCE == null){
                    INSTANCE = new SingLeton04();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(SingLeton04.getInstance())).start();
        }
    }
}

在初始化对象的过程中可能存在指令重排序,如果此时对象发生逃逸可能会引起并发导致的一致性问题

初始化对象过程可分为以下三步:

1.分配空间 2.执行构造,初始化 3. 指向对象

多线程场景下,为防止JVM重排指令(逻辑上) 应该用volatile修饰,禁止重排指令

java 复制代码
//volatile防止重排指令
private volatile static SingLeton04 INSTANCE;

总结

本篇文章主要描述懒汉式单例的3个坑

如果懒汉式单例提供获取实例的方法没有使用同步手段那么并发请求会破坏单例,因此需要加上同步手段

如果使用同步手段(加锁)粒度太大,会导致性能问题,因此可以使用双重检测锁

由于对象创建的过程可能会进行指令重排序,如果对象发生逃逸会造成一致性问题,因此需要使用volatile禁止指令重排序

相关推荐
用户120391129472619 分钟前
彻底搞定大模型流式输出:从二进制碎块到“嘚嘚嘚”打字机效果,让底层逻辑飞起来
前端·javascript·面试
老华带你飞43 分钟前
社团管理|基于Java社团管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
kingmax542120081 小时前
高中数学试讲稿:《对数与指数之间的相互转化》
面试·教师资格
over6972 小时前
深入解析:基于 Vue 3 与 DeepSeek API 构建流式大模型聊天应用的完整实现
前端·javascript·面试
老华带你飞2 小时前
汽车销售|汽车报价|基于Java汽车销售系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端·汽车
NPE~2 小时前
面试高频——分布式事务详解
分布式·面试·职场和发展·程序员·事务·分布式事务
uzong2 小时前
别让认知天花板,变成你的职业终点——技术人如何走出信息茧房
后端
华仔啊2 小时前
RebbitMQ 入门教程看这一篇就够了
java·后端·rabbitmq
无限进步_2 小时前
C语言实现贪吃蛇游戏详解
c语言·开发语言·数据结构·c++·后端·算法·游戏
豐儀麟阁贵3 小时前
9.5格式化字符串
java·开发语言·前端·面试