【设计模式】单例模式

发布于:2025-03-31 ⋅ 阅读:(23) ⋅ 点赞:(0)

案例介绍

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

案例代码

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() 显示主窗口。

网站公告

今日签到

点亮在社区的每一天
去签到