博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式系列——单例模式
阅读量:6160 次
发布时间:2019-06-21

本文共 2737 字,大约阅读时间需要 9 分钟。

单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。当系统需要某个类只能有一个实例时,就可以采用单例模式。

保证单例模式仅有一个实例的核心思想是构造方法私有化,即不允许外部调用该类的构造方法。基于此思想,主要有以下两种实现方式:

直接实例化

直接实例化这种方式也称作“饿汉式”,它直接定义了静态成员变量 s,并通过 new Singleton() 完成了初始化,之后不再变化,是线程安全的。 这种方式也存在一定的资源浪费,当没有使用 Singleton 对象时,程序依然会创建 Singleton 对象。

public class Singleton {	private Singleton() {}	private static final Singleton s = new Singleton();	public static Singleton getInstance() {		return s;	}}复制代码

延迟实例化

既然直接实例化浪费资源,那么我们是否可以考虑,在程序需要该对象的时候才创建它呢?当然可以! 与直接实例化稍不同,单例成员变量 s 初始为 null,它在方法 getInstance() 内部完成延迟实例化,并返回单例对象。

public class Singleton {	private Singleton() {}	private static Singleton s = null;	public static Singleton getInstance() {		if (s == null) {			s = new Singleton();		}		return s;	}}复制代码

这种方式存在线程安全问题。例如,假设两个线程调用 getInstance() 方法,线程 1 执行完 if(s == null),条件成立,在执行实例化语句 s = new Singleton() 之前,线程 2 来了,此时线程 2 执行 if(s == null),依然成立,进入 if 语句体。这种情况带来的后果是:程序两次创建了对象,这并不符合我们对单例模式的定义。

针对这种情况,可以有以下四种解决方法:

完全同步

完全同步方法,是在方法上加上 synchronized 同步。当多线程同时访问 getInstance() 方法时,多线程是“串行”的。

public class Singleton {	private Singleton() {}	private static Singleton s = null;	public static synchronized Singleton getInstance() {		if (s == null) {			s = new Singleton();		}		return s;	}}复制代码

这种方法,多线程每次访问 getInstance() 都必须“串行”运行,效率比较低。

部分同步

部分同步方法通过双重锁部分同步机制获得单例对象。因为代码中有两行相同的语句 if(s == null),故而叫做双重锁。第一个 if 语句可并行,当多线程均满足该条件, synchronized 修饰的代码必须串行运行。这样的话,其实只需要在第一次创建对象(通过了第一个 if 判断)的时候进行同步,效率较高。

public class Singleton {	private Singleton() {}	private volatile static Singleton s = null;	public static Singleton getInstance() {		if (s == null) {			synchronized(Singleton.class) {				if (s == null) {					s = new Singleton();				}			}		}		return s;	}}复制代码

注意,volatile关键字是确保当 s 被初始化成 Singleton 实例时,多个线程可以正确处理 s,即内存可见性

静态内部类

通过静态内部类 Inner 来实现单例对象。虚拟机加载应用程序字节码时,单例对象并不会立即创建,当第一次运行 Inner.s 时,单例对象才动态生成。这种实现方式无 synchronized 关键字,提高了效率。

public class Singleton {	private Singleton() {}	private static class Inner {		private static final Singleton s = new Singleton();	}	public static Singleton getInstance() {		return Inner.s;	}}复制代码

枚举

这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。调用的时候只需要 Singleton.INSTANCE 即可。

public enum Singleton {	INSTANCE;	// var here	public int var;	// methods here	public void otherMethods() {		System.out.println("write other methods here...");	}}复制代码

enum 实现 Singleton 的三个特性:自由序列化线程安全保证单例

首先, enum 是由 class 实现的,它可以有 member 和 member function。另外,由于 enum 是通过继承 Enum 类实现的,enum 结构不能作为子类继承其他类,但可以用来实现接口。此外 enum 类不能被继承,在反编译中,可以发现该类由 final 修饰。

其次,enum 有且仅有 private 的构造器,防止外部的额外构造,这恰好与单例模式吻合。

而对于序列化和反序列化,因为每一个枚举类型和枚举变量在 JVM 中都是唯一的,即 Java在序列化和反序列化枚举时做了特殊的规定,枚举的 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject 会破坏单例的问题。

(完)


参考资料

?

? 《Java 设计模式及应用案例分析》
?

转载地址:http://rylfa.baihongyu.com/

你可能感兴趣的文章
在C#调用C++的DLL简析(二)—— 生成托管dll
查看>>
Linux macos 常用终端操作
查看>>
企业网络的管理思路
查看>>
Linux磁盘分区与挂载
查看>>
J2se学习笔记一
查看>>
DNS视图及日志系统
查看>>
老李分享:Android性能优化之内存泄漏 3
查看>>
mysql命令
查看>>
来自极客标签10款最新设计素材-系列七
查看>>
极客技术专题【009期】:web技术开发小技巧
查看>>
PHP 简单计算器代码实现
查看>>
正则表达式的知识普及
查看>>
docker使用笔记
查看>>
华为eNSP模拟器上实现FTP服务
查看>>
【全球AI人才排行榜】美国第一,中国仅排名第7
查看>>
微信小程序输入框input
查看>>
MySql字符串函数使用技巧
查看>>
Doc2Vec,Word2Vec文本相似度 初体验。
查看>>
系统ghost后变成一个盘了别的分区的文件怎么找回
查看>>
Win7+Ubuntu11
查看>>