单例模式
来源:互联网 发布:svn默认端口号是多少 编辑:程序博客网 时间:2024/06/03 19:38
单例模式
转自:http://www.cnblogs.com/codingexperience/p/5796852.html
介绍
单例模式(Singleton Pattern)可能是设计模式中用的最多的模式,单例模式非常简单同时代码也比较短方便手写代码,所以也是面试中经常会问到的设计模式。单例模式它是一种对象创建模式,它用于确保系统中一个类只产生一个实例。例如:一个系统使用AppConfig对象读取诸如xml和properties配置文件,而当系统运行时,能够存在多个AppConfig对象,而每一个AppConfig对象中都封装着配置文件,这样非常浪费内存资源,因此系统内部只需一个该配置对象,这里就需要使用单例模式了。
本文针结合网上的资料对单例模式的常有的写法进行整理,希望对你有一定帮助。
最直接的单例模式
当面试题被问到这一题的时候,很多人的根据直觉可能写出下面的代码。
public
class
Singleton01 {
private
static
Singleton01 instance;
private
Singleton01() {}
public
static
Singleton01 getInstance() {
if
(instance ==
null
) {
instance =
new
Singleton01();
}
return
instance;
}
}
上面的代码简单明了,是很多人的最终答案,上面的代码还使用懒加载模式(就是需要使用实例对象时,才创建对象)。但有个明显的缺陷,就是只能在单线程中使用,在多线程时,会创建多个实例,造成内存泄漏。
线程安全的单例模式
遇到多线程的问题,自然而然地想到加锁,对获取实例方法加锁后,多线程情况下就可以保证线程安全。
public
class
Singleton02 {
private
static
Singleton02 instance;
private
Singleton02() { }
public
static
synchronized
Singleton02 getInstance() {
if
(instance ==
null
) {
instance =
new
Singleton02();
}
return
instance;
}
}
但上面的代码,虽然是线程安全,但是每一次使用getInstance()获取实例时,都必须加同步锁,加锁是很耗时的操作,在没有必要的时候我们应该尽量避免。
双重检验锁
对于上面的效率不高的问题,我们可以使用双重检验锁(Double-checked locking)来进行解决,我们应该在实例还没有创建之前需要加锁操作,以保证只有一个实例,当实例创建之后,就不再需要做加锁操作,在此过程中,会两次检查instance==null,称为double checked,其中一次在同步块外,一次在同步块内,至于为什么在同步块内还要检验一次,是因为可能多个线程都进行if代码中,如不进行检验,就可能创建多个实例。
public
class
Singleton03 {
private
static
Singleton03 instance;
private
Singleton03() { }
public
static
synchronized
Singleton03 getInstance() {
if
(instance ==
null
) {
// Single check
synchronized
(Singleton03.
class
) {
if
(instance ==
null
) {
// double check
instance =
new
Singleton03();
}
}
}
return
instance;
}
}
上面的代码看上去非常完美,但是还存在问题,主要在于instance = new Singleton03();这一句,这个语句并不是一个原子操作,它可以分解为下面三步:
1. 给对象引用instance分配内存 ;
2. 调用Singleton03的构造函数来初始化对象成员变量;
3. 将生成的实例对象的内存地址赋值给对象引用instance。
然而上面的3个步骤并不是最终的执行顺序,JVM中即时编译器中存在指令重排序的优化,最后的执行顺序,可能是1-2-3,也可能是1-3-2,当为第二种情况时,线程一刚执行第3步,线程二直接返回instance,然后instance这个对象引用已经指向堆内存,但堆内存中却没有初始化,于是报错,对于这个问题将instance声明为volatile即可。
public
class
Singleton03 {
private
volatile
static
Singleton03 instance;
private
Singleton03() { }
public
static
synchronized
Singleton03 getInstance() {
if
(instance ==
null
) {
// Single check
synchronized
(Singleton03.
class
) {
if
(instance ==
null
) {
// double check
instance =
new
Singleton03();
}
}
}
return
instance;
}
}
饿汉式
上面的单例模式都是使用懒加载的方式,可以称为懒汉式,这里介绍的是饿汉式,顾名思义,实例对象在使用之间就创建好了,直接用就可以了。
public
class
Singleton04 {
private
final
static
Singleton04 INSTANCE =
new
Singleton04();
private
Singleton04() { }
public
static
Singleton04 getInstance() {
return
INSTANCE;
}
}
上面的代码中当classloader加载Singleton04类时就创建了该实例,之后可以直接使用,这样一定是线程安全的,但这样的话,我们将实例的创建委托给类加载器,可能与我们要求的行为可能不太一致,我们可能希望在使用getInstance方法才创建单例对象。
静态内部类
为了解决上面的问题,我们可以使用静态内部类的方法,既可以在第一次使用getInstance时创建单例对象,又可以保证同步安全。
public
class
Singleton05 {
private
static
class
SingletonHolder {
private
final
static
Singleton05 INSTANCE =
new
Singleton05();
}
private
Singleton05() { }
public
static
Singleton05 getInstance() {
return
SingletonHolder.INSTANCE;
}
}
上面的代码,SingletonHolder为私有静态类,只能通过getInstance访问,所以是懒加载的,同时获取实例时无需同步,没有性能上的损失。
枚举
最简单的方式居然是用枚举,让人意想不到。
public
enum
Singleton06 {
INSTANCE;
private
Singleton06() {
}
public
static
Singleton06 getInstance() {
return
INSTANCE;
}
}
使用枚举创建实例默认是线程安全的,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。上面的getInstance方法可以不要,可以直接使用Singleton06.INSTANCE进行使用。
总结
单例模式的特点:
1、类的实例对象只有一个;
2、可以通过类自身的方法创建实例对象 ;
3、系统使用的对象为同一个对象。
单例模式的使用方法:小小的单例模式同样有很多种不同的写法,但根据我的经验推荐大家使用双重检验锁的方式,或直接使用饿汉式的方式,使用枚举的方式实际工作中感觉很少使用。
单例模式的注意事项:单例模式它是有范围的,它只局限于类加载器(ClassLoader)中能保证实例唯一,当有不同的类加载器时,会出现不同的类加载器装载同一个类,从而产生多个实例,所以,应该尽量保证多个类加载器不会装载同一个单例类。
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- python 文本写入及文本替换练习
- 实习程序1
- 关于jQuery load()方法加载页面后台发生异常而前台页面加载失败且没有任何展示信息的问题处理
- 密码发生器--蓝桥杯java组历年真题
- oracle学习
- 单例模式
- Greendao3.0的A级使用(入门级)
- 纯HTML5 APP与原生APP的差距在哪?
- AndroidStudio取色器
- GameObject.FindGameObjectsWithTag
- luogu p1087 FBI树
- 2D利用rigidbody移动以及Invoke函数
- Monkey and Banana||HDU1069
- 【教程】完美解决windows10磁盘占用100%并出现卡顿、假死现象