案例介绍
为什么需要单例模式?
比如,一个游戏只能有一个音乐播放器(如果有两个就会声音打架),或者一台电脑只能有一个鼠标光标,这时候就用单例模式保护它!
案例代码
1.饿汉式(最简单,线程安全)
public class CandyJar {
// 1. 直接创建一个唯一的糖果罐(类加载时就创建)
private static final CandyJar instance = new CandyJar();
// 2. 禁止外部 new 对象(保护魔法规则)
private CandyJar() {}
// 3. 提供获取唯一实例的方法
public static CandyJar getInstance() {
return instance;
}
}
特点:简单、线程安全,但可能浪费资源(如果一直不用这个实例)。
2.懒汉式(延迟创建,线程不安全)
public class CandyJar {
private static CandyJar instance; // 先不创建
private CandyJar() {}
// 第一次调用时创建实例(延迟加载)
public static CandyJar getInstance() {
if (instance == null) {
instance = new CandyJar(); // 线程不安全!
}
return instance;
}
}
问题:多线程下可能创建多个实例!
3.懒汉式 + 同步锁(线程安全但效率低)
public class CandyJar {
private static CandyJar instance;
private CandyJar() {}
// 加锁保证线程安全,但每次调用都要锁,效率低
public static synchronized CandyJar getInstance() {
if (instance == null) {
instance = new CandyJar();
}
return instance;
}
}
缺点:性能差(锁的代价太高)。
4.双重检查锁(最优解,线程安全+高效)
public class CandyJar {
// 用 volatile 禁止指令重排序
private static volatile CandyJar instance;
private CandyJar() {}
public static CandyJar getInstance() {
if (instance == null) { // 第一次检查
synchronized (CandyJar.class) { // 加锁
if (instance == null) { // 第二次检查
instance = new CandyJar();
}
}
}
return instance;
}
}
为什么高效:只有第一次创建时会加锁,之后直接返回实例。
5.静态内部类(推荐写法,懒加载+线程安全)
public class CandyJar {
private CandyJar() {}
// 静态内部类在第一次使用时才会加载
private static class Holder {
private static final CandyJar instance = new CandyJar();
}
public static CandyJar getInstance() {
return Holder.instance;
}
}
优点:懒加载、线程安全、代码简洁(无锁)
6.枚举实现(最安全,防反射攻击)
public enum CandyJar {
INSTANCE; // 唯一实例
public void putCandy() {
// 其他方法...
}
}
用法:CandyJar.INSTANCE.putCandy();
优点:绝对单例、线程安全、自动防反射和序列化破坏。
案例场景
1.配置管理(Configuration Manager)
- 场景:系统需要读取全局配置文件(如数据库配置、日志配置等),所有模块共享同一份配置。
- 示例:
public class AppConfig { private static AppConfig instance; private Properties config; private AppConfig() { loadConfig(); // 初始化时加载配置文件 } public static AppConfig getInstance() { if (instance == null) { instance = new AppConfig(); } return instance; } private void loadConfig() { config = new Properties(); try (InputStream is = getClass().getResourceAsStream("app.properties")) { config.load(is); } catch (IOException e) { e.printStackTrace(); } } public String getProperty(String key) { return config.getProperty(key); } }
使用:所有模块通过 AppConfig.getInstance().getProperty("db.url") 获取配置,避免重复加载文件。
2.日志记录(Logger)
- 场景:日志系统需要全局唯一的实例,确保所有日志写入同一个文件或输出流。
- 示例:
public class Logger { private static Logger instance; private File logFile; private Logger() { logFile = new File("app.log"); // 初始化日志文件 } public static synchronized Logger getInstance() { if (instance == null) { instance = new Logger(); } return instance; } public void log(String message) { // 写入日志文件... } }
使用:Logger.getInstance().log("User logged in"),保证日志统一管理。
3.数据库连接池(Database Connection Pool)
- 场景:数据库连接是昂贵的资源,必须全局共享一个连接池,避免频繁创建和销毁连接。
- 示例:
public class ConnectionPool { private static ConnectionPool instance; private List<Connection> connections; private ConnectionPool() { // 初始化连接池(如创建10个连接) connections = new ArrayList<>(); for (int i = 0; i < 10; i++) { connections.add(createConnection()); } } public static ConnectionPool getInstance() { if (instance == null) { synchronized (ConnectionPool.class) { if (instance == null) { instance = new ConnectionPool(); } } } return instance; } public Connection getConnection() { // 从池中获取一个可用连接... } public void releaseConnection(Connection conn) { // 将连接放回池中... } }
使用:所有数据库操作通过 ConnectionPool.getInstance() 获取连接。
4.缓存系统(Cache Manager)
- 场景:缓存需要全局唯一实例,避免不同模块维护各自的缓存导致数据不一致。
- 示例:
public class CacheManager { private static CacheManager instance; private Map<String, Object> cache = new HashMap<>(); private CacheManager() {} public static CacheManager getInstance() { if (instance == null) { instance = new CacheManager(); } return instance; } public void put(String key, Object value) { cache.put(key, value); } public Object get(String key) { return cache.get(key); } }
使用:CacheManager.getInstance().put("user_123", userData)。
6.硬件设备访问(如打印机、鼠标)
- 场景:操作系统需要统一管理硬件资源,例如打印机任务队列。
- 示例:
public class PrinterManager { private static PrinterManager instance; private Queue<PrintTask> printQueue = new LinkedList<>(); private PrinterManager() {} public static PrinterManager getInstance() { if (instance == null) { instance = new PrinterManager(); } return instance; } public void addTask(PrintTask task) { printQueue.add(task); } public void printNext() { // 执行下一个打印任务... } }
使用:所有打印任务通过 PrinterManager.getInstance().addTask(task) 提交。
6.线程池(Thread Pool)
- 场景:线程池需要全局统一管理,避免创建过多线程导致资源耗尽。
- 示例:
public class ThreadPool { private static ThreadPool instance; private ExecutorService executor; private ThreadPool() { executor = Executors.newFixedThreadPool(10); // 初始化线程池 } public static ThreadPool getInstance() { if (instance == null) { synchronized (ThreadPool.class) { if (instance == null) { instance = new ThreadPool(); } } } return instance; } public void execute(Runnable task) { executor.execute(task); } }
使用:ThreadPool.getInstance().execute(() -> doSomething())。
7.GUI 应用程序中的主窗口管理
- 场景:在桌面应用中,通常只需要一个主窗口实例。
- 示例:
public class MainWindow { private static MainWindow instance; private JFrame frame; private MainWindow() { frame = new JFrame("Main Window"); // 初始化窗口... } public static MainWindow getInstance() { if (instance == null) { instance = new MainWindow(); } return instance; } public void show() { frame.setVisible(true); } }
使用:MainWindow.getInstance().show() 显示主窗口。