设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性
说明:设计模式系列文章是读刘伟
所著《设计模式的艺术之道(软件开发人员内功修炼之道)》
一书的阅读笔记。个人感觉这本书讲的不错,有兴趣推荐读一读。详细内容也可以看看此书作者的博客https://blog.csdn.net/LoveLion/article/details/17517213
。
模式概述
模式定义
实际开发中,我们会遇到这样的情况,为了节约系统资源或者数据的一致性(比如说全局的Config
、携带上下文信息的Context
等等),有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现,这就是单例模式的动机所在。
单例模式(
Singleton Pattern
): 确保某一个类只有一个实例,而且自己实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 它必须自行向整个系统提供这个实例
模式结构图
单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。单例模式结构图如下所示:
单例模式结构图中只包含一个单例角色:
Singleton(单例)
:在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
饿汉式单例与懒汉式单例
饿汉式单例
饿汉式单例
类是实现起来最简单的单例类。由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,典型代码如下:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
懒汉式单例
懒汉式单例
在第一次调用getInstance()
方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load
)或者懒加载技术,即需要的时候再加载实例,为避免多线程
环境下同时调用getInstance()
方法从而生成多个实例,需要确保线程安全,相应实现也就有多种方式。
第一种
方法可以使用关键字synchronized
,代码实现如下:
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
在getInstance()
方法前面增加了关键字synchronized
进行同步,以处理多线程同时访问的安全问题。我们知道使用synchronized
关键字最好是在离共享资源最近的位置加锁,这样同步带来的性能影响会减小。所以让人感觉
上面的实现可以优化为如下代码:
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
问题貌似得以解决,事实并非如此。如果使用以上代码来实现单例,还是会存在单例对象不唯一。原因如下:
假如在某一瞬间线程A
和线程B
都在调用getInstance()
方法,此时instance
对象为null
值,均能通过instance == null
的判断。由于实现了synchronized
加锁机制,线程A
进入synchronized
修饰的代码块中执行实例创建代码,线程B
处于排队等待状态,必须等待线程A
执行完毕后才可以进入synchronized
修饰的代码块。但当A
执行完毕时,线程B
并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized
中再进行一次(instance == null)
判断,这种方式称为双重检查锁定(Double-Check Locking)
。使用双重检查锁定实现的懒汉式单例类典型代码如下所示:
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
// 第一重判断
if (instance == null) {
// 使用synchronized关键字加锁
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
需要注意的是,如果使用双重检查锁定
来实现懒汉式单例类,最好在静态成员变量instance
之前增加修饰符volatile
,被volatile
修饰的变量可以保证多线程环境下的可见性以及禁止指令重排序。由于volatile
关键字会屏蔽Java虚拟机所做的一些优化,可能对执行效率稍微有些影响,因此使用双重检查锁定来实现单例模式也不一定是最完美的实现方式。
如果是java
语言的程序,还可以使用静态内部类
的方式实现。代码如下:
public class Singleton {
private Singleton() {
}
private static class HolderClass {
final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
由于静态单例对象没有作为Singleton
的成员变量直接实例化,因此类加载
时不会实例化Singleton
,第一次调用getInstance()
时将加载内部类HolderClass
,在该内部类中定义了一个static
类型的变量instance
,此时会首先初始化这个变量,由Java虚拟机
来保证其线程安全性,确保该成员变量只初始化一次。
模式应用
模式在JDK中的应用
在JDK中,java.lang.Runtime
使用了饿汉式单例
,如下:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
模式在开源项目中的应用
Spring
框架中许多地方使用了单例模式
,这里随便举个例子,如org.springframework.aop.framework.ProxyFactoryBean
中的部分代码如下:
/**
* Return the singleton instance of this class's proxy object,
* lazily creating it if it hasn't been created already.
* @return the shared singleton proxy
*/
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// Initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
模式总结
单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。
主要优点
(1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
(2) 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
(3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。
适用场景
在以下情况下可以考虑使用单例模式:
(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性的更多相关文章
- 设计模式系列之单例模式(Singleton Pattern)
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式.这种模式涉及到一个单一的类,该类负责创建自己的对象 ...
- Net设计模式实例之单例模式( Singleton Pattern)
一.单例模式简介(Brief Introduction) 单例模式(Singleton Pattern),保证一个类只有一个实例,并提供一个访问它的全局访问点.单例模式因为Singleton封装它的唯 ...
- 浅谈设计模式--单例模式(Singleton Pattern)
题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...
- 设计模式之单例模式(Singleton Pattern)
单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...
- 【设计模式】单例模式 Singleton Pattern
通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance) 的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...
- 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)
原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...
- 二十四种设计模式:单例模式(Singleton Pattern)
单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...
- 【白话设计模式四】单例模式(Singleton)
转自:https://my.oschina.net/xianggao/blog/616385 0 系列目录 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factor ...
- 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)
在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...
随机推荐
- Vue学习—— Vuex学习笔记
组件是Vue最强大的功能之一,而组件实例的作用域是相互独立的,意味着不同组件之间的数据是无法相互使用.组件间如何传递数据就显得至关重要,这篇文章主要是介绍Vuex.尽量以通俗易懂的实例讲述这其中的差别 ...
- 数据库SQL语言从入门到精通--Part 4--SQL语言中的模式、基本表、视图
数据库从入门到精通合集(超详细,学习数据库必看) 前言: 使用SQL语言时,要注意SQL语言对大小写并不敏感,一般使用大写.所有符号一定是西文标点符号(虽然是常识,但我还是提一嘴) 1.模式的定义与删 ...
- postman(环境设置)
1.点击小齿轮进入到环境变量添加页面,点击add添加环境变量 2.新增环境输入变量名称和变量值 3.添加成功 4.接口中设置变量,切换环境进行传参 5.调用环境变量断言 调用环境变量中的phone变量 ...
- 最新Idea超实用告别996插件,都是免费
Idea告别996插件 在IntelliJ IDEA中,秉着IDEA自带能实现的快捷方式就不用插件的原则,少用些插件,运行性能也提升一些,虽然很少,哈哈.分享下我个人常用的插件,希望对大家有些帮助.插 ...
- 题解 CF545A 【Toy Cars】
题目传送门 太弱了,只能写写A题的题解 题意 给你一个 $n·n$ 的矩阵,翻车分三种情况: 如果 $a_i,_j=1$ ,记录第 $i$ 辆车 如果 $a_i,_j=2$ ,记录第 $j$ 辆车 如 ...
- 最短路 西北大学2019年春季校赛 ( 重现赛 ) 房间迷宫 求一个数的所有的约数nlogn
题目:https://www.cometoj.com/contest/33/problem/G?problem_id=1461(密码:jwjtxdy) 学习一下 求一个数的约数 复杂度n*logn # ...
- 关于使用ffmpeg的一些牢骚
一.啰嗦几句 好几年不写博客了,一是工作计算机都加密了没法编辑提交:二是各种语言混用,什么都会就是什么都不会,delphi.c#.vb.python.c++要说我精通啥,啥也不精,所以不敢乱写. 最近 ...
- inode block 软硬链接
inode block 软硬链接 1 inode 1.1 inode(索引节点)作用 (1)用于存储文件数据属性信息(2)用于存储数据指针信息 1.2 如何产生 格式化时,创建文件系统 1.3 如何查 ...
- 动态代理学习(一)自己动手模拟JDK动态代理
最近一直在学习Spring的源码,Spring底层大量使用了动态代理.所以花一些时间对动态代理的知识做一下总结. 我们自己动手模拟一个动态代理 对JDK动态代理的源码进行分析 文章目录 场景: 思路: ...
- 记一次jackson序列化Boolean的坑
@Data public class CouponTemplateDto { /** * 优惠券类型id */ private Long couponTypeId; /** * 优惠券模板id */ ...