文章目录
多线程环境下玩单例模式,简直就是程序员与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?
- 外层检查避免每次都进同步块
- 内层检查防止重复创建
- 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();
}
}
(冷知识预警)这种写法保证每个线程有且仅有一个实例!适合需要线程隔离的场景,比如数据库连接管理。但注意这已经不是传统意义上的单例了!
实战选型指南
- 简单场景直接用枚举(Java5+项目首选)
- 需要懒加载用静态内部类
- 考虑反射攻击时选枚举
- 特殊场景考虑ThreadLocal方案
- 千万别用懒汉式!(重要的事情说三遍)
(血泪教训)在Spring框架中,默认Bean作用域就是单例的,但人家是通过容器保证的线程安全。自己手写单例时,一定要根据业务场景选择最合适的实现方式!
下次面试官再让你手写单例,请优雅地甩出五种写法+原理分析,保证让TA眼前一亮!(战术后仰)