Java中单例模式的几种写法

本文深入解析单例模式的五种实现方法,包括非线程安全、线程安全、双重检查锁定、静态工厂及枚举实现,探讨每种方法的优缺点及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 单例模式:确保一个类只有一个实例 (也就是类的对象),并且提供一个全局的访问点 (外部通过这个访问点来访问这个类的唯一实例)

二、单例模式的几种写法:

1. 第一种:非线程安全写法

public class SingletonOne {
	private static SingletonOne instance = null;
	
	/*
	 * 将构造方法声明为私有的,这样可以保证在类的外部无法实例化该类 (即,在类的外部获取不到类的实例)
	 */
	private SingletonOne(){
	}
	
	/*
	 * 提供一个全局的访问点 (其实就是一个全局静态字段),
	 * 外部可以通过该字段访问该类的唯一实例;
	 * 因为静态字段是属于类的,因此这样可以保证只有一个实例。
	 */
	private static SingletonOne getInstance(){
		if(instance == null){
			instance = new SingletonOne();
		}
		return instance;
	}
}

注:这种实现方法不是线程安全的,所以在项目实践中如果涉及到线程安全问题时,就不会使用这种方式;但如果不需要保证线程安全,则这种方式还是不错的选择,因为其所需要的开销比较小。

2. 第二种:线程安全写法

public class SingletonOne {
	private static SingletonOne instance = null;
	
	/*
	 * 将构造方法声明为私有的,这样可以保证在类的外部无法实例化该类 (即,在类的外部获取不到类的实例)
	 */
	private SingletonOne(){
	}
	
	/*
	 * 提供一个全局的访问点 (其实就是一个全局静态字段),
	 * 外部可以通过该字段访问该类的唯一实例;
	 * 因为静态字段是属于类的,因此这样可以保证只有一个实例。
	 */
	private static synchronized SingletonOne getInstance(){
		if(instance == null){
			instance = new SingletonOne();
		}
		return instance;
	}
}

注:(1) 在getInstance()方法添加synchronized关键字进行修饰,就可以实现方法的同步了;但是这样系统开销会很大;

       (2) 每次有线程调用getInstance()方法时,都需要进行同步判断。

3. 第三种:两种 lazy loaded thread-safe 的单例模式实现方式

    (1) DCL (double checked locking 实现法) ,顾名思义,就是双检查法;检查实例 instance 是否为 null 或者已经实例化。

public class DoubleCheckLockingSingleton {
	private volatile DoubleCheckLockingSingleton instance;
	private DoubleCheckLockingSingleton(){}
	public DoubleCheckLockingSingleton getInstance(){
		if(instance == null){
			synchronized (DoubleCheckLockingSingleton.class) {
				if(instance == null){
					instance = new DoubleCheckLockingSingleton();
				}
			}
		}
		return instance;
	}
}

     注:此方法中,对 instance 进行了两次 null 判断:若第一次不为空,则直接返回实例;若为空,则进入同步代码块再次进行 null 值判断,再判断是否实例化。第一次的 null 值判断可以减少系统开销。在实际项目中做过多线程开发的都应该知道 DCL。

     (2) lazy initialization holder class 模式实现方式

public class SingletonTwo {
	/*
	 * 类级别的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
	 * 而且只有被调用到才会装载,从而实现了延迟加载。
	 */
	private static class SingletonHolder{
		// 静态初始化器,由JVM来保证线程安全
		private static SingletonTwo instance = new  SingletonTwo();
	}
	
	/*
	 * 私有化构造方法
	 */
	private SingletonTwo(){}
	
	public static SingletonTwo getInstance(){
		return SingletonHolder.instance;
	}
}

       注:当getInstance()方法第一次被调用时,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化时,会初始化它的静态域,从而创建 SingletonTwo 的实例,由于是静态域,因此只会被虚拟机在装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这种模式的优势在于,getInstance() 方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

           关于“延迟初始化” (lazy loaded):”除非绝对必要,否则就不要延迟初始化“。延迟初始化是一把双刃剑,它降低了初始化类或者创建实例的开销,却增加了访问被延迟初始化的域的开销,考虑到延迟初始化的域最终需要初始化的开销以及域的访问开销,延迟初始化实际上降低了性能。

4. 第四种:静态工厂实现法

    因为单例是静态的final变量,当类第一次加载到内存中的时候就初始化了,其thread-safe性由JVM来负责保证,值得注意的是这个实现方式不是lazy-loaded的。

public class SingletonThree {
	private static final SingletonThree instance = new SingletonThree();
	
	private SingletonThree(){}
	
	public SingletonThree getInstance(){
		return instance;
	}
}

5. 第五种:枚举实现单例 

    枚举单例 (Enum Singleton) 是实现单例模式的一种新方式,枚举这个特性是Java5才出现,在《Effective Java》一书中有介绍这个特性。

public enum SingletonFour {
	INSTANCE("hello"){
		public void someMethod(){
			
		}
	};
	
	private String name;
	private SingletonFour() {
	}
	private SingletonFour(String name) {
		this.name = name;
	}
	
	private void printName(){
		System.out.println(name);
	}
	protected abstract void someMethod();
}

    可以通过SingletonFour.INSTANCE 来访问该单例变量。默认枚举实例的创建时线程安全的,但是在枚举中的其他任何方法由程序员自己负责。如果使用这种实例方法,那么就需要考虑确保线程安全(如果它影响到其他对象的状态的话)。传统单例存在的另一个问题是一旦实现了序列化接口,那么它们不再保持单例了,但是枚举单例,JVM对序列化有保证。枚举实现单例的好处:有序列化和线程安全的保证,代码简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值