23种设计模式——1. 单例模式

概念

单例模式:在计算机进程中,同一个类始终只有一个对象进行操作

多例模式:在计算机进程中,对一个实体类创建异常对象就是单个对象操作;若创建多个对象则分别对应多个对象操作。

应用场景

  • 数据连接池管理 确保整个应用共享同一个连接池,避免频繁创建/销毁连接池带来的性能损耗。
  • 日志记录系统 单例日志保证所有模块写入同一日志文件,防止多实例导致的日志混乱或文件冲突。
  • Windows任务管理器 操作系统只能打卡一个任务管理器窗口,通过单例模式实现进程监控唯一入口。

使用

单例模式包含以下写法:

模式 说明 建议/不建议原因
饿汉模式 ❎ 最开始时就创建对象 未实现懒加载,可能造成资源浪费
懒汉模式(非线程安全)❎ 首次调用时创建实例 多线程环境下可能会创建多个实例
加锁的懒汉式单例(synchronized )❎ 首次使用的时候创建对象。但需要防止多线程,所以需要上锁。 同步锁导致性能下降。即使示例已经创建。
懒汉模式:双重检测说模式 ❎ 首次使用的时候创建对象,同时防止多线程。如果单例已创建,则,则不上锁。 对比加载的懒汉式单例,性能得到了提升。适合创建时性能开销比较少的场景
静态内部类✅ 属于半懒汉、半饿汉式的单例
静态内部类开始时不会加载,需要的时候才会加载,由于这个类一加载就会创建对象。所以实现了懒汉的资源不滥用,饿汉的防止多线程
没有资源浪费和多线程问题。(不过无法防止反射攻击创建新实例)
枚举单例✔️ 通过枚举类实现单例 线程安全且代码简洁;不适用于继承其他类;防止反射攻击
  1. 饿汉模式❎

    /**
     * 单例模式1:饿汉式 (不建议)
     * 优点:线程安全,在类加载时就创建了单例对象,不会出现线程安全问题。
     * 缺点:如果单例对象没有被使用,就浪费了内存。
     * @Author:lyj
     * @Date:2025/4/28 15:53
     */
    public class Singleton1 {
        private static Singleton1 instance = new Singleton1();  // 引用全局唯一的单例对象,在一开始就创建好
    
        /**
         * 私有构造函数,防止外部创建实例
         */
        private Singleton1() {
        }
    
        public static Singleton1 getInstance() {    // 获取唯一的单例对象
            return instance;
        }
    }
    
  2. 懒汉模式 ❎

    /**
    - 单例模式2:懒汉模式
    - @Author:lyj
    - @Date:2025/4/28 16:17
    - @Filename:Singleton2
    */
    public class Singleton2 {
    private static Singleton2 instance;
    /**
    
    - 私有构造函数,防止外部创建实例
    */
     private Singleton2(){
    
    }
    public static Singleton2 getInstance(){
     //如果实例为空,则创建实例 ;否则直接返回实例
     if(instance == null){   
       instance = new Singleton2();
     }
     return instance;
    }
    
    public static void main(String[] args) {
    
    }
    

    以下语句,可能造成多个对象。

    if(instance == null){   
    instance = new Singleton2();
    }
    

    另一个线程可能在instance创建前,同时判断instance为空,也创建了一个对象。为了更好的展示,可以把方法改为:

    if(instance == null){
    try {
    	Thread.sleep(1000);     // 模拟耗时操作
    } catch (InterruptedException e) {
    	throw new RuntimeException(e);
    }
    instance = new Singleton2();
    

    此时,多线程示例:

    // 测试多线程下的单例模式
    for (int i = 0; i < 10; i++) {
    	new Thread(() -> {
    		Singleton2 instance = Singleton2.getInstance();
    		System.out.println(instance);
    	}).start();
    }
    

    可以发现,创建了不同的对象。
    image

  3. 加锁的懒汉模式 (synchronized)❎

    /**
     * 单例模式:加锁懒汉模式 (synchronized)
     * @Author:lyj
     * @Date:2025/4/28 19:10
     */
    public class Singleton3 {
        private static volatile Singleton3 instance;    // volatile修饰,表示该变量在多线程下可见
        private Singleton3(){}  // 禁用构造方法
        public static synchronized Singleton3 getInstance(){
            if(instance==null){     // 第一次外访问时不加锁
                instance = new Singleton3();
            }
            return instance;
        }
    }
    
  4. 懒汉模式:双重检测锁定(DCL) ❎

    /** 懒汉模式:双重检测机制(DCL)
     * @Author:lyj
     * @Date:2025/5/4 10:06
     */
    public class Singleton4 {
        private static volatile Singleton4 instance;
        private Singleton4(){}
        public static Singleton4 getInstance(){
            if(instance == null){       // 第一次外访问
                synchronized (Singleton4.class){    // 加锁
                    if(instance == null){           // 第两次内访问
                        instance = new Singleton4();    // 新建
                    }
                }
            }
            return instance;
        }
    }
    
  5. 静态内部类 ✔️

    /**
     * 单例模式:静态内部类
     * @Author:lyj
     * @Date:2025/5/4 10:39
     */
    public class Single5 {
        // 私有化构造方法
        private Single5(){}
        private static class SingletonHolder{
            // 由静态内部类创建单例。
            private static final Single5 INSTANCE = new Single5();
        }
        public static Single5 getInstance(){
            // 只有首次使用内部类时,才会进行类初始化
            return SingletonHolder.INSTANCE;
        }
    }
    

    静态内部类刚开始时不会加载,需要的时候才会加载。由于这个类一加载就会创建对象。所以,实现了懒汉的资源不滥用,饿汉式防止多线程

  6. 枚举单例✔️

    /**- 单例: 枚举单例
    - @Author:lyj
    - @name:Singleton6
    
    - @Date:2025/5/4 11:56
    */
    public enum Singleton6 {
    	INSTANCE;       // 唯一实例,由JVM线程安全
    
    	// 私有构造函数
    	private Singleton6()  {}
    
    	/**
    
    	- 公共方法
    	*/
    	 public void doSomething()  {
    	 System.out.println("doSomething");
    	 }
    }
    

    枚举单例以及简的代码实现高性能,安全的单例模式。虽然,是不是严格的延迟加载,但其综合优势远超其他实现,适合绝大多数的场景。

私有构造函数,防止实例化

在以上单例的例子中,禁用构造函数的方式都是直接使用私有化。已知,私有的方法,外部直接使用 Singleton1 singleton1 = new Singleton1();是错误的。如果使用映射。

public static void main(String[] args) {
	try {
	   
		Class<?> clazz = Class.forName("com.lyj.singleton.Singleton1");
		Constructor<?> constructor = clazz.getDeclaredConstructor();        // 创建无参构造函数
		constructor.setAccessible(true);
		for (int i = 0; i < 10; i++){
			Singleton1 instance1 = (Singleton1) constructor.newInstance();  // 创建实例
			System.out.println("i=>" + i + ": " + instance1);
		}
	} catch (ClassNotFoundException e) {
		throw new RuntimeException(e);
	} catch (NoSuchMethodException e) {
		throw new RuntimeException(e);
	} catch (InvocationTargetException e) {
		throw new RuntimeException(e);
	} catch (InstantiationException e) {
		throw new RuntimeException(e);
	} catch (IllegalAccessException e) {
		throw new RuntimeException(e);
	}
}

运行后可以发现,是不同的单例。
image
所以,可以在私有化方法中加入判断。

/**
 * 私有构造函数,防止外部创建实例
 */
private Singleton1() {
	if (instance != null) {
		throw new IllegalArgumentException("单例对象已经创建,禁止重复创建");
	}
}
posted @ 2025-06-20 15:11  陆陆无为而治者  阅读(34)  评论(0)    收藏  举报