为什么说单例模式是 Java 开发里的 “全局独苗”?一篇讲透它的核心逻辑
刚接触java设计模式的时候,我被“单例”这两个字绕的有点懵——直到用“家里的冰箱”做了一个类比,我才恍然大悟,它的本质:整个程序中。某一个类只能有一个实例,所有人用的都是同一个。今天就从实际场景出发,给大家聊聊单例模式的核心特点,以及常用实现方式,以及呢什么时候该使用它。
一.单例模式是什么?
在 Java 中,单例模式(Singleton Pattern)是一种创建型设计模式,其核心目的是确保某个类在整个应用程序中只有一个实例,并提供一个全局访问点来获取这个实例。
单例模式的特点:
1.唯一实例:类只能有一个实例对象。
2.自行创建:类必须自己创建这个唯一实例。
3.全局访问:类必须提供一个全局访问点供外部获取该实例。
看完了是不是有点懵,哈哈哈哈。
二.先搞懂:单例模式到底在解决什么问题?
我们可以想象两个生活场景:
家里的冰箱:不可能为了放饮料再买一个,为了放蔬菜又买一个——对于全家来说共用一个就够了,多了反而占地方,管理麻烦;
游戏里面的背景音乐播放器:如果同时存在两个播放器,一个放战斗的音乐,一个放动画的音乐,画面直接混乱了,必须只有一个“全局播放器”。
那么对应到java开发中,单例模式解决的就是“避免重复创建实例”的问题。比如:日志工具类:如果每个模块都new一个日志对象,日志会分散到多个文件里,根本没法统一查看;
这些场景的共性,就是需要“全局唯一”实例
三.单例模式的三个“铁律”
想要去实现一个合格的单例,必须满足3个条件,缺一不可:
1.实例必须是“唯一”的:整个程序只能有一个。
2.自己创建“实例”:不能靠外部创建。
3.提供“全局入口”:别人能拿到实例。
四.两种常用实现:饿汉模式vs静态内部类模式
1.饿汉模式:“提前备好,随时随取”
饿汉模式用生活例子说:就像你早上起床肯定要喝水,所以睡前就把一杯水提前准备好放在床头,早上一醒就能直接喝到 —— 这杯水(实例)是提前创建好的。
饿汉模式的特点:
- 类一加载到内存,就立刻创建好唯一的实例
- 不管你现在用不用,先把对象创建出来等着
举个例子:(日志工具)
public class LogUtil {
// 1. 类加载时就创建唯一实例,用private static final修饰(不可修改)
private static final LogUtil INSTANCE = new LogUtil();
// 2. 私有构造方法:禁止外部new
private LogUtil() {}
// 3. 提供全局访问入口:返回提前创建好的实例
public static LogUtil getInstance() {
return INSTANCE;
}
// 业务方法:比如打印日志
public void log(String message) {
System.out.println("[" + System.currentTimeMillis() + "] " + message);
}
}
优缺点分析:
优点:简单!不用处理线程安全问题;
缺点:缺点很明显,可能浪费资源。如果这个实例很大,且程序运行中一直没用到,它还是会占用内存。
2.静态内部类模式:“按需创建,不浪费资源”
如果觉得饿汉模式“太浪费”,那静态内部类模式就是最好的选择——它能做到“用的时候再创建实例”,也就是我们常说的“懒加载”。
它的核心逻辑很巧妙:用“静态内部类”来持有唯一的实例,只有当外部调用getInstance()时候,内部类才会加载,实例才会被创建。
我们以“全局计分器”举例:
public class ScoreCounter {
// 1. 私有构造方法:禁止外部new
private ScoreCounter() {}
// 2. 静态内部类:专门用来持有唯一实例
private static class CounterHolder {
// 内部类加载时,才创建外部类的实例
private static final ScoreCounter INSTANCE = new ScoreCounter();
}
// 3. 全局访问入口:调用时才触发内部类加载
public static ScoreCounter getInstance() {
return CounterHolder.INSTANCE;
}
// 业务方法:比如加分
private int score = 0;
public void addScore(int num) {
score += num;
System.out.println("当前分数:" + score);
}
}
静态内部类模式的优缺点:
优点:兼顾“懒加载”和“线程安全”——既不会提前浪费资源。又不用加synchronifzed锁;代码也简洁。
缺点:如果实例创建时候需要传参数,这种方式就不太好处理(大部分单例场景不需要传参)。
五.避雷提醒:这些情况别用单例模式
单例模式虽好,但是不是万能的,以下场景强行用,反而会给自己挖坑:
1.需要多实例的场景:比如“用户对象”——每个用户都该有自己的实例,总不能所有用户共用一个姓名,一个ID吧?
2.频繁销毁和创建的场景:单例实例一旦创建,会一直存在到程序结束,如果某个对象需要频繁创建,用完就销毁,用单例反而会占内存;
3.测试困难的场景:单例的实例的全局唯一的,测试时候一个用例修改了实例状态,会影响其他用例的结果,比如测试“计分器”时,上一个用例把分数加到100,下一个用例就没法从0开始测试了。
六.总结
如果用一句话来总结单例模式,就是:“按需创建一个全局唯一的实例,用它来管理统一的资源或者状态”。
它不复杂,核心是抓住“唯一”和“全局访问”这两个点;选择实现方式,不用纠结——简单场景用饿汉模式,需要懒加载用静态内部类,就能满足大部分开发需求。
更多推荐


所有评论(0)