计算机系统应用教程网站

网站首页 > 技术文章 正文

Java 单例模式实战指南:手把手教你用代码征服它!

btikc 2025-01-21 15:28:25 技术文章 17 ℃ 0 评论

官方对单例设计模式的定义

确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

换句话说,单例模式强制实施以下约束:

  • 一个类只能有一个实例。
  • 必须提供一个全局访问点来获取该实例。
  • 客户端代码不应该能够自行实例化该类。

常见适用场景

重量级的对象,不需要多个实例。

如:(线程池,数据库连接池等)

  • 需要确保系统中只存在一个特定对象,例如日志记录器或数据库连接。
  • 需要控制对象的创建和销毁,例如对象池或缓存。
  • 需要提供一个全局访问点来访问某个对象,例如设置对象或注册表。

五种实现方案

  1. 懒汉模式

说明:懒汉模式是延迟加载的, 只有在真正使用的时候,才开始进行实例化的。

  • 优点

01、节省内存,避免了在应用启动时就创建不需要的对象。因为延迟加载原因,仅在首次使用到时才会进行初始化单例对象,而不是在类加载时就创建实例。

02、线程安全性可控,可以通过在获取实例的方法上双重加锁来确保线程安全。当多个线程同时调用获取实例的方法时,只有一个线程能够进入临界区,保证了实例的唯一性。

03、更具灵活性,因为延迟加载的原因,可以根据实际情况动态地加载或初始化实例,从而实现更灵活的对象管理和资源利用

  • 缺点

01、性能问题与复杂性问题,因为要保证线程是安全的,会在获取实例的方法上加是双重锁(即:double check),从而导致多个线程竞争锁资源,生成一定的性能问题

02、因编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化的实例(但这个可以通过添加 volatile 关键字进行修饰处理,因为 volatile 修饰的字段,可以防止指令重排问题)

  • 代码与原理说明
/**
 * @Author: denglinyong
 * @FileName: SingletonLazy
 * @Description: 懒汉模式
 */

public class SingletonLazy {
	//volatile 修饰的目的就是防止指令重排
	private volatile static SingletonLazy instance;
	private SingletonLazy(){}
	public static SingletonLazy getInstance(){
		if(instance == null){
			//如果不加锁的情况下,多线程就执行就会创建多个对象
			synchronized (SingletonLazy.class){
				//必须使用双重判断,因为在多线程的情况下大家同时进入到 synchronized (SingletonLazy.class) 这代码时,如果不再判断为空,那所有到这句话的线程synchronized (SingletonLazy.class)都会创建一次对象
				//因为大家都在等待获得锁,并不会再重新从头执行代码的,只会在 synchronized (SingletonLazy.class) 这会话等待锁,获得锁就会继续往下执行
				if(instance == null){
					instance = new SingletonLazy();
					//创建一个类型,在字节码层上会分为几步进行
					//1、分配空间
					//2、初始化
					//3、引用赋值
					//因为JIT CPU为了执行效率会对我们的代码进行重排,所以在真实运行的情况可能会出现如下顺序
					//1、分配空间
					//3、引用赋值
					//2、初始化
					//先选择初始化再进行赋值,如果出现这情况就会造成空指针异常问题
					//解决这个问题可以使用 volatile 来修饰 instance 变量,因为 volatile 有一个重要特性,就是防止指令重排功能
				}
			}
		}
		return instance;
	}

	public static void main(String[] args) {
		new Thread(() -> {
			SingletonLazy singletonLazy = SingletonLazy.getInstance();
			System.out.println(singletonLazy);
		}).start();
		new Thread(() -> {
			SingletonLazy singletonLazy = SingletonLazy.getInstance();
			System.out.println(singletonLazy);
		}).start();
	}
}
  1. 饿汉模式

说明:在类加载时就创建。因为类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。

代码与原理说明:

/**
 * 饿汉模式
 */
class SingletonHungry{
	//通过类加载来保证唯一
	//请参类加载过程
	private static SingletonHungry instance = new SingletonHungry();
	private SingletonHungry(){
		//防止别人通过反射来创建实例
		if (instance != null){
			throw new RuntimeException("单例不允许多个实例");
		}
	}

	public static SingletonHungry getInstance() {
		return instance;
	}
}
  1. 静态内部类

说明:在类被使用时创建。其本质上是利用类的加载机制来保证线程安全和实例唯一的,也是懒加载的形式之一。

代码与原理说明:

/**
 * 静态内部类
 * 懒加载模式
 */
class SingletonInnerClass{
	//通过类加载来保证唯一
	//请参类加载过程
	private static class SingletonInnerClassHolder{
		private static final SingletonInnerClass instance = new SingletonInnerClass();
	}
	private SingletonInnerClass(){
		//防止别人通过反射来创建实例
		if (SingletonInnerClassHolder.instance != null){
			throw new RuntimeException("单例不允许多个实例");
		}
	}
	public static SingletonInnerClass getInstance(){
		return SingletonInnerClassHolder.instance;
	}
}
  1. 枚举类型

说明:利用枚举类型的特性确保了在Java中实现单例的线程安全性和反序列化安全性。因为枚举类型是天然不支持反射创建对应实例的,且有自己的反序列化机制。

代码与原理说明:

public enum Singleton {
    INSTANCE;  // 唯一的枚举实例

    // 添加需要的实例方法
    public void doSomething() {
        // 这里添加实例方法的具体实现
    }
}
  1. 序列化

说明:因为序列化和反序列化时可能会破坏单例模式的特性(默认的序列化机制会通过创建新的对象来实现反序列化,从而导致单例被破坏)。为了解决这个问题,可以通过在单例类中添加特殊的方法来控制序列化和反序列化的行为。

代码与原理说明:

import java.io.Serializable;

public class Singleton implements Serializable {
    // 静态内部类持有单例实例
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 私有构造函数,禁止外部实例化
    private Singleton() {}

    // 获取单例实例的静态方法
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 重写 readResolve() 方法,确保反序列化时返回同一个实例
    protected Object readResolve() {
        return getInstance();
    }
}

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表