Java案例中的单例模式怎么写?

wen java案例 1

Java单例模式实战指南:从原理到代码案例的完整解析

目录

  1. 什么是单例模式?为什么需要它?
  2. 单例模式的几种经典实现方式
  3. 饿汉式 vs 懒汉式:如何选择?
  4. 线程安全的单例:DCL与静态内部类
  5. 枚举单例:最简洁的解决方案
  6. 实战案例:使用单例管理数据库连接
  7. 常见问题与避坑指南

什么是单例模式?为什么需要它?

Q:单例模式的核心思想是什么?
A:单例模式确保一个类在JVM中只有一个实例,并提供全局访问点,它常用于管理共享资源(如线程池、缓存、配置对象),避免频繁创建和销毁对象带来的性能开销。

Java案例中的单例模式怎么写?

Q:何时必须用单例?举个例子
A:当某个对象需要被整个系统共享且状态唯一时,比如应用配置管理器,如果多次创建,会导致内存浪费或数据不一致,日志记录器如果存在多个实例,日志可能重复写入。


单例模式的几种经典实现方式

在Java中,单例主要通过以下几种方式实现:

代码案例1:饿汉式(线程安全但可能浪费内存)
public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() {} // 私有构造防止外部实例化
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

优点:简单、线程安全(JVM加载时即创建)。
缺点:无论是否使用,类加载时就创建实例,可能增加启动时间。

代码案例2:懒汉式(非线程安全版)
public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton(); // 多线程可能创建多个
        }
        return instance;
    }
}

问题:多个线程同时调用getInstance()可能创建多个实例,需加锁。


饿汉式 vs 懒汉式:如何选择?

Q:项目启动慢,优先选哪种?
A:懒汉式,它延迟初始化直到真正调用,适合创建成本高或依赖外部配置的对象,饿汉式则适合轻量级且一定会被使用的对象。

Q:懒汉式如何保证线程安全?
A:给getInstance()方法加synchronized关键字,但直接加在方法上会导致每次调用都同步,性能差,优化方案见下一节。


线程安全的单例:DCL与静态内部类

方案1:双重检查锁定(DCL)
public class DCLSingleton {
    private static volatile DCLSingleton instance; // volatile保证可见性
    private DCLSingleton() {}
    public static DCLSingleton getInstance() {
        if (instance == null) {                // 第一次检查
            synchronized (DCLSingleton.class) {
                if (instance == null) {        // 第二次检查
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

关键点volatile禁止指令重排,避免部分构造的对象被其他线程读取,这是目前最常用的线程安全懒汉式。

方案2:静态内部类(基于类加载机制)
public class InnerClassSingleton {
    private InnerClassSingleton() {}
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优势:由JVM保证线程安全,且延迟加载(只有调用getInstance时才会加载内部类)。


枚举单例:最简洁的解决方案

Q:为什么推荐枚举实现单例?
A:枚举天生具备序列化安全、线程安全,且代码极简,Joshua Bloch在《Effective Java》中强烈推荐。

public enum EnumSingleton {
    INSTANCE;
    public void doSomething() {
        System.out.println("枚举单例方法调用");
    }
}

使用场景:需要序列化/反序列化单例时,枚举自动防御反射攻击和克隆破坏。


实战案例:使用单例管理数据库连接

假设你的应用需要全局共享一个数据库连接池,实现如下:

import java.sql.Connection;
import java.sql.DriverManager;
public class DBConnectionManager {
    private static volatile DBConnectionManager instance;
    private Connection connection;
    private DBConnectionManager() {
        try {
            // 加载驱动并创建连接(实际请用连接池)
            Class.forName("com.mysql.cj.jdbc.Driver");
            this.connection = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb", "user", "pass");
        } catch (Exception e) {
            throw new RuntimeException("连接数据库失败", e);
        }
    }
    public static DBConnectionManager getInstance() {
        if (instance == null) {
            synchronized (DBConnectionManager.class) {
                if (instance == null) {
                    instance = new DBConnectionManager();
                }
            }
        }
        return instance;
    }
    public Connection getConnection() {
        return connection;
    }
}

调用方式DBConnectionManager.getInstance().getConnection(),整个应用共享同一个连接,避免反复创建。


常见问题与避坑指南

Q1:反射能破坏单例吗?如何防御?
A:通过反射调用私有构造器可以创建新实例,防御方案:在构造器中检查实例是否已存在,若存在则抛出异常;或用枚举单例(天然免疫反射)。

Q2:序列化会破坏单例吗?
A:如果类实现了Serializable,反序列化时会创建新对象,解决方案:添加readResolve()方法返回现有单例实例;或使用枚举单例。

Q3:单例与Spring的Bean作用域是什么关系?
A:Spring默认Bean也是单例,但由容器管理,无需自行编码,但理解原生单例有助于理解Spring底层原理。

Q4:单例在分布式系统下是否失效?
A:单例仅保证一个JVM内唯一,分布式场景需使用其他方案(如Redis锁、数据库表锁),但这是另一个话题。


单例模式是Java开发者必须掌握的经典设计模式之一,从饿汉式到枚举,每种实现都有其适用场景,在实际开发中,优先推荐静态内部类或枚举实现,既保证线程安全,又避免性能开销,理解这些代码案例,你将能在面试和项目中游刃有余地使用单例模式。

抱歉,评论功能暂时关闭!