设计模式-单例模式



单例模式属于创建型模式

它只允许一个实例的存在,提供访问其唯一对象实例的方式,
避免频繁创建与销毁实例。

实现方式

单例模式实现方式有枚举、私有构造器两类。

私有构造器

这种实现方式最直观。

1 懒汉式

直到使用前才会创建实例。

package com.superv.resource.design.single;

public class Singleton {

	/**
	 * Singleton对象唯一实例
	 */
	private static Singleton instance;

	/**
	 * 私有构造器
	 */
	private Singleton() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	/**
	 * 访问Singleton对象唯一实例方法
	 * @return
	 */
	public static Singleton getInstance () {
		if (null == instance) { // 对象尚未被实例化
			instance = new Singleton();
		}
		return instance;
	}
	
	public static void main(String[] args) {
		Singleton instance1 = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		
		System.out.println(instance1 == instance2); // true
	}
	
}

不过这样在并发的情况下,可能会创建出不止一个实例。
在getInstance ()方法添加synchronized关键字,可以解决线程安全问题。
但是会导致效率低下。

2 饿汉式

类加载的时候就创建实例。

package com.superv.resource.design.single;

public class Singleton {

	/**
	 * Singleton对象唯一实例
	 */
	private static Singleton instance = new Singleton();

	/**
	 * 私有构造器
	 */
	private Singleton() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	/**
	 * 访问Singleton对象唯一实例方法
	 * @return
	 */
	public static synchronized Singleton getInstance () {
		return instance;
	}
	
	public static void main(String[] args) {
		Singleton instance1 = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		
		System.out.println(instance1 == instance2); // true
	}
	
}

这样就不会存在线程安全的问题,
因为Java JVM类加载的特性,可以保证单实例。
可是有可能会产生很多无用的实例。

3 双重检查锁(推荐)

双重检查锁也是一种懒加载,
并且很好地解决了线程安全时效率低下的问题。

package com.superv.resource.design.single;

public class Singleton {

	/**
	 * Singleton对象唯一实例
	 */
	private static volatile Singleton instance;
	
	/**
	 * 私有构造器
	 */
	private Singleton() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	/**
	 * 访问Singleton对象唯一实例方法
	 * @return
	 */
	public static synchronized Singleton getInstance () {
		if (null == instance) { // 对象尚未被实例化
			synchronized (Singleton.class) {
				if (null == instance) { // 再次进行非空判断,以防加锁过程中对象被实例化
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
	
	public static void main(String[] args) {
		Singleton instance1 = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		
		System.out.println(instance1 == instance2); // true
	}
	
}

// volatile:若变量在线程栈被修改,会强制同步到进程栈,
// 确保变化对其它的线程立即可见。

在程序执行过程中,出于性能考虑,在某些情况下会发生指令重排。
指令重排,是在字节码指令的层面,
在这里,对象实例化不是原子性的,有可能会出现下面的情况:
在这里插入图片描述
注:正常情况初始化顺序应为a、c、b。

volatile关键字可以避免指令重排,保证以a、c、b的顺序执行,
就不会出现上面的问题。

如果对volatile如何避免指令重排感兴趣,可以翻看文章下边的参考资料。

4 静态内部类(推荐)

静态内部类利用了类加载的特性确保单实例,同时又实现了延迟加载。

package com.superv.resource.design.single;

public class Singleton {

	private static class SingletonHolder {
		
		/**
		 * Singleton对象唯一实例
		 */
		private static volatile Singleton instance = new Singleton();
		
	}
	
	/**
	 * 私有构造器
	 */
	private Singleton() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	/**
	 * 访问Singleton对象唯一实例方法
	 * @return
	 */
	public static synchronized Singleton getInstance () {
		return SingletonHolder.instance;
	}
	
	public static void main(String[] args) {
		Singleton instance1 = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		
		System.out.println(instance1 == instance2); // true
	}
	
}

Java JVM类加载时候不会加载静态内部类,使用的时候才会进行加载。

枚举

这种实现方式最优雅。

5 枚举(推荐)

代码简单到无法理解。

package com.superv.resource.design.single;

public enum Singleton {

	INSTANCE;
	
	public static void main(String[] args) {
		Singleton instance1 = Singleton.INSTANCE;
		Singleton instance2 = Singleton.INSTANCE;
		
		System.out.println(instance1 == instance2); // true
	}
	
}

实现简单,并且通过反射等方法也无法破坏单例特性,是最为推荐的方式。

不当之处,请予指正。

参考资料:

vipwhr单例模式五种实现
jsbintask22从未这么明白的设计模式(一):单例模式
一指流砂~深入理解设计模式(一):单例模式

Wan_shibugongvolatile关键字的作用
猴子007volatile关键字的作用、原理
大道方圆Java内存模型与指令重排
HelloWorld搬运工volatile对指令重排的影响

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值