单例模式的实现及其演变

前言

单例模式是结构最简单的设计模式,在它的核心结构中只包含一个被称为单例类的特殊类。
作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中的使用频率相当高,在很多应用软件和框架中都得以广泛应用。

单例模式的结构

单例模式的结构图如下:
单例模式结构图
其Java代码实现如下:

public class Singleton {
	private static Singleton instance = null;//静态私有成员变量
	//私有构造函数
	private Singleton() {
	
	}
	//静态公有工厂方法,返回唯一实例
	public static Singleton getInstance() {
		if(instance == null)
			instance = new Singleton();
		return instance;
	}
}

在单例模式的实现过程中要注意以下3点:
(1)单例类构造函数的可见性为private。
(2)提供一个类型为自身的静态私有成员变量。
(3)提供一个公有的静态工厂方法。

演变

1、饿汉式单例

饿汉式单例是实现起来最简单的单例类,其结构图如下:
饿汉式单例类图

代码如下:

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

当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。

2、懒汉式单例类与双重检查锁定

懒汉式单例类的结构图如图:
懒汉式单例类图
从图中可以看出,懒汉式单例类在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要时再加载实例。
代码如下:

public class LazySingleton {
	private static LazySingleton instance = null;
	private LazySingleton() {
	
	}
	
	//使用synchronized关键字对方法加锁,确保任意时刻只有一个线程可执行该方法
	public synchronized static LazySingleton getInstance() {
		if(instance==null){
			instance = new LazySingleton();
		}
		return instance;
	}
}

上述代码虽然解决了线程安全问题,但每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发环境中将导致系统性能大大降低。
对getInstance()进行改进:

public staticLazySingleton getInstance() {
		if(instance==null){
			synchronized (LazySingleton.class){
				instance = new LazySingleton();
			}
		}
		return instance;
	}

问题解决了吗?并没有。
假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null,均能通过判断,由于实现了synchronized加锁机制,线程A进入锁定代码中执行实例创建动作,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定的代码。但当A线程执行完毕时线程B将继续创建新的实例,导致产生多个单例对象,违背了单例模式的设计思想,因此需要进一步改进,进行双重检查锁定(Double-Check Locking):

public class LazySingleton {
	private volatile static LazySingleton instance = null;
	private LazySingleton() {
	
	}
	
	public synchronized static LazySingleton getInstance() {
		//第一重判断
		if(instance==null){
			//锁定代码块
			synchronized (LazySingleton.class){
				//第二重判断
				if(instance==null){
					instance = new LazySingleton();//创建单例实例
				}
			}
		}
		return instance;
	}
}

这里需要使用volatile来修饰instance,这样可以确保多个线程都能够正确处理。
由于volatile关键字会屏蔽Jvm所作的一些代码优化,可能会导致系统运行效率降低,因此使用双重检查锁定来实现单例模式也不算完美。

3、使用静态内部类实现单例模式

饿汉式单例类不能实现延迟加载,不管用不用始终占据内存;
懒汉式单例类线程安全控制繁琐,而且性能受影响。
为了克服以上问题,Java使用Initialization on Demand Holder(IoDH)技术来实现单例模式。
实现代码如下:

//Initialization on Demand Holder(IoDH)
public class Singleton {
	private Singleton(){}

	//静态内部类
	private static class HolderClass{
		private final static Singleton instance = new Singleton();
	}

	public static Singleton getInstance(){
		return HolderClass.instance;
	}


}

由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()将加载内部类HolderClass,该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由JVM保证其线程安全性,确保只初始化一次,而且getInstance()方法没有任何线程锁定,因此性能不会受到影响。


参考书籍:《JAVA设计模式》第8章单例模式 刘伟 编著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值