【设计模式】单例模式

案例介绍

为什么需要单例模式?

比如,一个游戏只能有一个音乐播放器 (如果有两个就会声音打架),或者一台电脑只能有一个鼠标光标,这时候就用单例模式保护它!

案例代码

1.饿汉式(最简单,线程安全)

java 复制代码
public class CandyJar {

    // 1. 直接创建一个唯一的糖果罐(类加载时就创建)

    private static final CandyJar instance = new CandyJar();

    // 2. 禁止外部 new 对象(保护魔法规则)

    private CandyJar() {}



    // 3. 提供获取唯一实例的方法

    public static CandyJar getInstance() {

        return instance;

    }

}

特点:简单、线程安全,但可能浪费资源(如果一直不用这个实例)。

2.懒汉式(延迟创建,线程不安全)

java 复制代码
public class CandyJar {

    private static CandyJar instance; // 先不创建

    private CandyJar() {}

    // 第一次调用时创建实例(延迟加载)

    public static CandyJar getInstance() {

        if (instance == null) {

            instance = new CandyJar(); // 线程不安全!

        }

        return instance;

    }

}

问题:多线程下可能创建多个实例!

3.懒汉式 + 同步锁(线程安全但效率低)

java 复制代码
public class CandyJar {

    private static CandyJar instance;

    private CandyJar() {}

    // 加锁保证线程安全,但每次调用都要锁,效率低

    public static synchronized CandyJar getInstance() {

        if (instance == null) {

            instance = new CandyJar();

        }

        return instance;

    }

}

缺点:性能差(锁的代价太高)。

4.双重检查锁(最优解,线程安全+高效)

java 复制代码
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.静态内部类(推荐写法,懒加载+线程安全)

java 复制代码
public class CandyJar {

    private CandyJar() {}

    // 静态内部类在第一次使用时才会加载

    private static class Holder {

        private static final CandyJar instance = new CandyJar();

    }

    public static CandyJar getInstance() {

        return Holder.instance;

    }

}

优点:懒加载、线程安全、代码简洁(无锁)

6.枚举实现(最安全,防反射攻击)

java 复制代码
public enum CandyJar {

    INSTANCE; // 唯一实例

    public void putCandy() {

        // 其他方法...

    }

}

用法 :CandyJar.INSTANCE.putCandy();
优点:绝对单例、线程安全、自动防反射和序列化破坏。

案例场景

1.配置管理(Configuration Manager)

  • 场景:系统需要读取全局配置文件(如数据库配置、日志配置等),所有模块共享同一份配置。

  • 示例

    java 复制代码
    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)

  • 场景:日志系统需要全局唯一的实例,确保所有日志写入同一个文件或输出流。

  • 示例

    java 复制代码
    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)

  • 场景:数据库连接是昂贵的资源,必须全局共享一个连接池,避免频繁创建和销毁连接。

  • 示例

    java 复制代码
    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)

  • 场景:缓存需要全局唯一实例,避免不同模块维护各自的缓存导致数据不一致。

  • 示例

    java 复制代码
    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.硬件设备访问(如打印机、鼠标)

  • 场景:操作系统需要统一管理硬件资源,例如打印机任务队列。

  • 示例

    java 复制代码
    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)

  • 场景:线程池需要全局统一管理,避免创建过多线程导致资源耗尽。

  • 示例

    java 复制代码
    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 应用程序中的主窗口管理

  • 场景:在桌面应用中,通常只需要一个主窗口实例。

  • 示例

    java 复制代码
    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() 显示主窗口。

相关推荐
海特伟业3 小时前
隧道调频广播覆盖-隧道调频广播无线覆盖系统建设要点、难点分析与解决应对
运维·设计模式
sg_knight3 小时前
设计模式实战:享元模式(Flyweight)
python·设计模式·享元模式·flyweight
Swift社区6 小时前
AI 时代,ArkUI 的设计模式会改变吗?
人工智能·设计模式
数据中穿行6 小时前
访问者设计模式全方位深度解析
设计模式
宁雨桥6 小时前
前端设计模式面试题大全
前端·设计模式
数据中穿行7 小时前
迭代器设计模式全方位深度解析
设计模式
数据中穿行7 小时前
观察者设计模式全方位深度解析
设计模式
程序员Terry8 小时前
别老写重复代码了!模版方法模式一次讲透
java·设计模式
数据中穿行8 小时前
建造者模式全方位深度解析
设计模式
数据中穿行8 小时前
组合设计模式全方位深度解析
设计模式