单例模式与线程安全的攻防战(手撕七种写法!)

多线程环境下玩单例模式,简直就是程序员与JVM的极限拉扯!(拍桌)今天咱们就掰开了揉碎了,把单例模式的七种写法挨个扒个底朝天!

一、饿汉式:最刚的直男

public class HungrySingleton {
    // 程序启动就new对象(直接怼内存里)
    private static final HungrySingleton instance = new HungrySingleton();
    
    private HungrySingleton() {} // 构造器私有
    
    public static HungrySingleton getInstance() {
        return instance;
    }
}

这种写法线程绝对安全!(敲黑板)但缺点也很明显——就算不用这个实例,启动时也会占用内存。就像家里囤了十年用量的厕纸,虽然安全但占地方啊!

二、懒汉式:翻车重灾区

public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {}
    
    public static LazySingleton getInstance() {
        if (instance == null) { // 第一重检查
            instance = new LazySingleton();
        }
        return instance;
    }
}

看似美好,实则暗藏杀机!(危)当多个线程同时通过第一重检查时,可能会创建多个实例。这个写法在多线程环境下就是个定时炸弹!

三、同步方法版:安全但低效

public class SyncMethodSingleton {
    private static SyncMethodSingleton instance;
    
    private SyncMethodSingleton() {}
    
    public static synchronized SyncMethodSingleton getInstance() {
        if (instance == null) {
            instance = new SyncMethodSingleton();
        }
        return instance;
    }
}

给方法加synchronized锁确实解决了线程安全问题,但每次获取实例都要抢锁!(性能警告)就像进自家厕所还要排队等钥匙,太憋屈了!

四、双重检查锁:教科书级方案

public class DCLSingleton {
    private volatile static DCLSingleton instance;
    
    private DCLSingleton() {}
    
    public static DCLSingleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (DCLSingleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

(灵魂拷问)为什么要双重检查?为什么要用volatile?

  1. 外层检查避免每次都进同步块
  2. 内层检查防止重复创建
  3. volatile禁止指令重排序(重点!new操作不是原子性的)

五、静态内部类:优雅永不过时

public class HolderSingleton {
    private HolderSingleton() {}
    
    private static class Holder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }
    
    public static HolderSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

(拍腿叫绝)利用类加载机制保证线程安全!Holder类只有在被调用时才会加载,完美实现懒加载+线程安全。这才是教科书级别的实现!

六、枚举单例:终结者的选择

public enum EnumSingleton {
    INSTANCE;
    
    public void doSomething() {
        // 业务方法
    }
}

(重磅推荐)Joshua Bloch在《Effective Java》中力推的方式!不仅能避免反射攻击,还能自动处理序列化问题。缺点是很多老项目还不习惯用枚举做单例。

七、ThreadLocal单例:另类玩法

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> instance =
        ThreadLocal.withInitial(ThreadLocalSingleton::new);
    
    private ThreadLocalSingleton() {}
    
    public static ThreadLocalSingleton getInstance() {
        return instance.get();
    }
}

(冷知识预警)这种写法保证每个线程有且仅有一个实例!适合需要线程隔离的场景,比如数据库连接管理。但注意这已经不是传统意义上的单例了!

实战选型指南

  1. 简单场景直接用枚举(Java5+项目首选)
  2. 需要懒加载用静态内部类
  3. 考虑反射攻击时选枚举
  4. 特殊场景考虑ThreadLocal方案
  5. 千万别用懒汉式!(重要的事情说三遍)

(血泪教训)在Spring框架中,默认Bean作用域就是单例的,但人家是通过容器保证的线程安全。自己手写单例时,一定要根据业务场景选择最合适的实现方式!

下次面试官再让你手写单例,请优雅地甩出五种写法+原理分析,保证让TA眼前一亮!(战术后仰)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值