懒汉式单例的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禁止指令重排序

相关推荐
CoderYanger20 分钟前
A.每日一题:2095. 删除链表的中间节点
java·数据结构·程序人生·leetcode·链表·面试·职场和发展
Csvn23 分钟前
日志管理与排查 — journalctl & 系统日志实战
后端
周末也要写八哥25 分钟前
面经经验分享|熟练掌握面试考点
经验分享·面试·职场和发展
zhenlai20121 小时前
Vue3 + SpringBoot + AI:我做了一个股票分析工具(第1周复盘)
人工智能·spring boot·后端
CoderYanger1 小时前
A.每日一题:3612. 用特殊操作处理字符串 I
java·程序人生·leetcode·面试·职场和发展·学习方法·改行学it
Oneslide9 小时前
Ubuntu 26.04 完整安装 Fcitx5 中文拼音输入法指南(适配默认Wayland)
后端
huangdong_9 小时前
电商平台图片URL原图转换技术深度解析:从缩略图到高清原图的完整方案
java·后端·spring
掘金码甲哥10 小时前
3min手搓一个帮助文档站,很合理吧!
后端
一只旭宝10 小时前
【C++入门精讲22】常见设计模式
c++·设计模式
JAVA面经实录91710 小时前
操作系统面试题
java·服务器·数据库·计算机网络·面试