设计模式:11.单例模式及其8种实现方式对比

131 阅读8分钟

单例模式

1.基本介绍

(1) 定义

采取一定的方法,保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态)。

(2) 8种方式
  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查-推荐使用
  • 静态内部类-推荐使用
  • 枚举-推荐使用

2.饿汉式(静态常量)

(1) 步骤
  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部创建该类的对象实例。
  • 向外暴露一个静态的公共方法getInstance(),通过该方法返回一个该类的对象实例。
(2) 代码实现
/** 
* 饿汉式(静态常量)实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){
	}
	
	// 2. 在类的内部创建该类的对象实例
	private final static Singleton instance = new Singleton();

	// 3. 向外暴露一个静态的公共方法,返回实例对象
	public static Singleton getInstance(){
		return instance;
	}
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.getInstance();
		Singleton test2 = Singleton.getInstance();
	}
}
(3) 优缺点
  • 优点:写法简单,在类装载的时候就完成实例化。避免了线程同步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到懒加载(Lazy Loading)的效果。如果从始至终没有用过这个实例,则会造成内存浪费。
  • 如果是调用getInstance()方法导致类装载是没有问题的,但是如果还有其他方式导致类装载,这时候初始化的instance对象实例就没有使用,也就是没有达到lazy loading效果。
(4) 结论

这种单例模式可用,可能造成内存浪费。


3.饿汉式(静态代码块)

(1) 步骤
  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 在静态代码块中,创建该类的实例对象。
  • 向外暴露一个静态的公共方法getInstance(),通过该方法返回一个该类的对象实例。
(2) 代码实现
/** 
* 饿汉式(静态代码块)实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){
	}

	// 2. 在类的内部声明该类的实例对象
	private static Singleton instance;
	
	// 3. 在静态代码块中,创建该类的实例对象
	static {
		instance = new Singleton();
	}
	
	// 4. 向外暴露一个静态的公共方法,返回实例对象
	public static Singleton getInstance(){
		return instance;
	}
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.getInstance();
		Singleton test2 = Singleton.getInstance();
	}
}
(3) 优缺点
  • 优缺点和饿汉式(静态常量)相同。
(4) 结论

这种单例模式可用,可能造成内存浪费。


4.懒汉式(线程不安全)

(1) 步骤
  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。
(2) 代码实现
/** 
* 懒汉式(线程不安全)实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){}
	
	// 2. 在类的内部声明该类的实例对象
	private static Singleton instance;

	// 3. 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。懒汉:即用到的时候,才去创建
	public static Singleton getInstance(){
		if(null == instance){
			instance = new Singleton();
		}
		return instance;
	}
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.getInstance();
		Singleton test2 = Singleton.getInstance();
	}
}
(3) 优缺点
  • 优点:用到的时候才去创建,起到了Lazy Loading效果
  • 缺点:线程不安全,在多线程的情况下,一个线程进入了if(null == instance),还没来的及往下执行,另一个线程也通过这个判断语句,这时会产生多个实例。
(4) 结论

多线程情况下,产生了多个实例,即破坏了单例模式的设计初衷。在实际开发过程中,不要使用这种方式。


5.懒汉式(线程安全,同步方法)

(1) 步骤
  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法是同步方法,只能一个线程执行。
(2) 代码实现
/** 
* 懒汉式(线程安全)实现单例模式 
**/
public class Singleton{
	private Singleton(){}
	
	private static Singleton instance;

	// 3. 将该方法声明为同步方法,当一个线程在执行这个方法是,其他线程等待。
	public static synchronized Singleton getInstance(){
		if(null == instance){
			instance = new Singleton();
		}
		return instance;
	}
}
(3) 优缺点
  • 优点: 解决了线程安全问题。
  • 缺点: 效率低,每个线程在想获取该类的实例的时候,执行getInstance()方法,都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获取该类的实例,直接return就行了。
(4) 结论

在实际开发中,不推荐使用这种方法。


6.懒汉式(线程安全,同步代码块)

(1) 步骤
  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法中创建实例对象的代码是同步的,只能一个线程执行。
(2) 代码实现
/** 
* 懒汉式(线程安全)实现单例模式 
**/
public class Singleton{
	private Singleton(){}
	
	private static Singleton instance;

	public static synchronized Singleton getInstance(){
		if(null == instance){
			// 同步代码块,如果一个线程在执行,其他线程等待
			synchronized(Singleton.class){
				instance = new Singleton();
			}
		}
		return instance;
	}
}
(3) 优缺点
  • 缺点:本意是对同步方法效率低问题的改进,但是,这种写法并没有实现线程安全,因为多线程一旦进入if(null == instance)就会往下执行,即使等待上一个线程执行完new,也会创建多个实例。
(4) 结论

这种写法是错误的。


7.双重检查-推荐使用

(1) 步骤
  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象,该变量声明为共享变量。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法中创建实例对象的代码是同步的,只能一个线程执行。新增双重检查
(2) 代码实现
/** 
* 双重检查实现单例模式 
**/
public class Singleton{
	private Singleton(){}
	
	// volatile修饰后,该变量变为共享变量,一个线程更改了它的值后,会立刻刷新到主存(内存)中,其他线程再读取会读取到最新的值
	private static volatile Singleton instance;

	public static synchronized Singleton getInstance(){
		if(null == instance){	// 第一重检查
			// 只有一个线程进入执行,其他线程等待
			synchronized(Singleton.class){
				if(null == instance){	// 第二重检查
					// 该线程一旦创建该类的实例对象,其他线程过不了第二重检查
					instance = new Singleton();	
				}
			}
		}
		return instance;
	}
}
(3) 优缺点
  • 优点: 双重检查(Double-Check)概念是多线程开发中经常使用到的。保证了线程安全,避免了反复进行方法同步效率高,实现了Lazy Loading。
(4) 结论

在实际开发中,推荐使用这种方法实现单例模式


8.静态内部类-推荐使用

(1) 步骤
  • 构造器私有化
  • 创建一个静态内部类,该内部类中有一个静态属性Singleton
  • 提供静态公有方法,直接返回该静态内部类的静态属性
(2) 代码实现
/** 
* 静态内部类实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){}

	// 2. 创建一个静态内部类,该内部类中有一个静态属性Singleton
	private static class SingletonInstance{
		private static final Singleton INSTANCE = new Singleton();
	}

	// 3. 提供静态公有方法,直接返回该静态内部类的静态属性
	public static Singleton getInstance(){
		return SingletonInstance.INSTANCE;
	}
}
(3) 分析
  • 在Singleton类装载的时候,内部类SingletonInstance是不装载的,在调用getInstance()的时候才装载内部类,因此实现了懒加载,用到的时候才创建实例对象。
  • 类的静态属性只会在第一次加载类的时候初始化,在类的初始化时,是线程安全的。JVM帮助我们实现了线程安全。
  • JVM在类装载的时候,是线程安全的,因此使用了JVM底层的机制实现线程安全。
(4) 优缺点
  • 优点:线程安全、懒加载、效率高。
(5) 结论

推荐使用


9.枚举-推荐使用

(1) 步骤
(2) 代码实现
/** 
* 枚举实现单例模式 
**/
enum Singleton{
	INSTANCE;	// 属性
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.INSTANCE;
		Singleton test2 = Singleton.INSTANCE;
	}
}
(4) 优缺点
  • 优点:借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。
(5) 结论

推荐使用


10.JDK源码实例

Runtime runtime = Runtime.getRuntime();

在这里插入图片描述
采用的是饿汉式静态常量的方式实现的单例模式。