设计模式之单例(Singleton)模式

本文详细介绍了单例模式的实现方式,包括最简单的单例模式、延迟创建、线程安全及双重检查锁定等,并探讨了不同场景下的优劣。

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

如果要保证系统在一个类最多只能存在一个实例时,我们就需要单例模式。这种情况在应用中经常碰到,例如缓存池、数据库连接池、线程池、一些应用服务实例等等。在多线程环境中。为了保证实例的唯一性其实并不简单。


1、最简单的单例模式

为了限制该类的对象被随意的创建,需要保证该类构造方法是私有的,这样外部类就无法创建该类的对象;另外,为了方便给客户对象提供单例对象的使用,我们为提供一个全局访问点,如下:



package com.pattern.singleton;

public class Singleton {
	private static Singleton instance = new Singleton();

	private Singleton() {

	}
	public static Singleton getInstance() {
		return instance;
	}
}
Singleton类中只有一个构造方法,它是被private修饰的,客户对象无法创建该对象的实例。
我们为此单例实现了一个全局访问点 public static Singleton getInstance()方法。
注意:instance 变量是私有的,外界无法访问。此实现是线程安全的,当多个线程同时去访问该类getInstance()方法时,不会初始化多个不同的对象,因为JVM在加载此类的时候,对于static数据的初始化只能由一个线程执行且仅一次。

2、单例性能-延迟创建
如果出于性能方面的考虑,我们希望延迟实例化单例对象(static属性在加载类时就会被初始化),只有在第一次使用该类的实例时才去实例化。此时我们就可以使用延迟创建,我们可以把单例的实例化过程移至getInstance()方法中,而不是在加载时预先创建。当访问该方法时,首先判断该实例对象是不是已经被实例化过了,如果已被初始化,则直接返回这个对象的引用;否则,创建这个实例并初始化,如下:

package com.pattern.singleton;

public class UnThreadSafeSingleton {
	private static UnThreadSafeSingleton instance = null;

	public static UnThreadSafeSingleton getInstance() {
		if (instance == null) {
			instance = new UnThreadSafeSingleton();
		}
		return instance;
	}
}
 注意: if(instance == null) 判断是否实例化完成了,该方法不是线程安全的。

2.1、线程安全
在高并发的环境中,getInstance()方法返回了多个指向不同的实例对象,原因如何呢?

    Thread1  Thread2

1 if(instance==null)

2

3 instance = new UnThreadSafeSingelton();

4 return instance;

5

6

1

2 if(instance==null)

3

4 instance = new UnThreadSafeSingelton();

5 return instance;

6


如果这两个线程按照上述步骤执行,我们不难发现,在时刻1和2,由于还没创建单例对象,Thread1和Thread2都会进入创建单例的代码快分别创建实例。在时刻3 Thread1创建了一个实例对象,但是Thread2此时无法知道,于是继续创建一个新的实例对象,导致这两个线程持有的实例并非为同一个。

更为糟糕的是,在没有自动内存回收机制的语言平台(C++)会因为我们认为创建了一个单例对象,从而忽略了其他线程所产生的对象,不会手动去回收他们,从而引起内存泄露。
为了解决此类问题,我们给方法添加 synchronized关键字,如下:
package com.pattern.singleton;

public class UnThreadSafeSingleton {
	private static UnThreadSafeSingleton instance = null;

	public static synchronized UnThreadSafeSingleton getInstance() {
		if (instance == null) {
			instance = new UnThreadSafeSingleton();
		}
		return instance;
	}
}
这样,再多的线程访问都只会实例化一个单例对象。

3、Double-Check Locking
虽然在多线程环境下是线程安全了,但是在多线程高并发的情况下,给次方法加上synchronized关键字会是的性能大不如前。synchronized关键字对整个getInstance()方法同步是没有必要的:我们只要保证实例化这个对象的那段逻辑被一个线程执行就可以了,而返回引用的那段代码是没有必要同步的。如下:

package com.pattern.singleton;

public class DoubleCheckSingleton {
	private volatile static DoubleCheckSingleton instance = null;

	public static DoubleCheckSingleton getInstance() {
		if (instance == null) {	// check if it is created.
			synchronized (DoubleCheckSingleton.class) {	// synchronized creation block
				if (instance == null) { // double check if it is created
					instance = new DoubleCheckSingleton();
				}
			}
		}
		return instance;
	}
}
 
注意: 在getInstance()方法里,首先判断次实例是否被创建了,如果还没有创建,首先使用synchronized同步实例化代码块。在同步代码块里,还需要再次检查是否已经创建了单例对象,因为:如果没有第二次检查,这是有两个线程 Thread A 和 Thrad B 同时进入该方法,他们都检测到instance 为null 不管那个线程先占据了同步锁,并创建了实例对象,都不会阻止另外一个线程进入实例代码块重新创建实例对象,这样,同样会生成两个实例对象,所以,我们在同步的代码块中,要进行第二次判断,判断该对象是否被创建。
属性instance是被volatile修饰的,因为volatile具有synchronized的可见性特点,也就是说线程能够自动发现volatile变量的最新值。这样,如果instance实例化成功,其他线程便能立刻发现。

4、Initialization on demand holder

package com.pattern.singleton;

public class LazyLoadedSingleton {
	private LazyLoadedSingleton() {

	}

	private static class LazyHolder {
		private static final LazyLoadedSingleton instance = new LazyLoadedSingleton();
	}

	public static LazyLoadedSingleton getInstance() {
		return LazyHolder.instance;
	}
}
 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值