什么是单例设计模式? (转)
来源:互联网 发布:制作条形码的软件 编辑:程序博客网 时间:2024/04/30 08:21
http://mp.weixin.qq.com/s/1ORmn0hTN5_z9WU2sP7CMw
什么是单例设计模式?
简单来说,就是一个类只能构建一个对象的设计模式。(一个类只有一个实例。即一个类只有一个对象实例)
单例模式第一版(非线程安全):
public class Singleton { private static Singleton instance = null; //(懒汉模式需判空操作) //或写成 private static Singleton instance = new Singleton(); (饿汉模式) private Singleton() {} //私有构造函数 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}
为什么这样写呢?我们来解释几个关键点:
1.要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。
2.instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。
3.getInstance是获取单例对象的方法。
如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式(被动构建,需要判空操作)。
如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。
这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。
为什么说刚才的代码不是线程安全呢?
假设Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance方法:
public class SingletonTest { //私有的实例对象 private static SingletonTest instance; //私有的构造方法 private SingletonTest(){ } public static SingletonTest getInstance(){ if(instance == null){ 线程A> instance = new SingletonTest(); <线程B } return instance; }}
因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:
这样一来,显然instance被构建了两次。让我们对代码做一下修改:
单例模式第二版(线程安全):
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 **synchronized (Singleton.class){ //同步锁** if (instance == null) { //双重检测机制 instance = new Singleton(); } } } return instance; }}
为什么这样写呢?我们来解释几个关键点:
1.为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。
2.进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 线程A> synchronized (Singleton.class){ //同步锁 <线程B if (instance == null) { //双重检测机制 instance = new Singleton(); } } } return instance; }}
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 synchronized (Singleton.class){ //同步锁 <线程B if (instance == null) { //双重检测机制 线程A> instance = new Singleton(); } } } return instance; }}
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 synchronized (Singleton.class){ //同步锁 <线程B if (instance == null) { //双重检测机制 <线程B instance = new Singleton(); } } } 线程A> return instance; }}
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 synchronized (Singleton.class){ //同步锁 if (instance == null) { //双重检测机制 <线程B instance = new Singleton(); } } } 线程A> return instance; <线程B }}
像这样两次判空的机制叫做双重检测机制。
————————————
进一步改进
假设这样的场景,当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法:
这种情况表面看似没什么问题,要么Instance还没被线程A构建,线程B执行 if(instance == null)的时候得到false;要么Instance已经被线程A构建完成,线程B执行 if(instance == null)的时候得到true。
真的如此吗?答案是否定的。这里涉及到了JVM编译器的指令重排。
指令重排是什么意思呢?比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:
memory =allocate(); //1:分配对象的内存空间
instance =memory; //3:设置instance指向刚分配的内存地址
ctorInstance(memory); //2:初始化对象
当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。如下图所示:
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 <线程B synchronized (Singleton.class){ //同步锁 if (instance == null) { //双重检测机制 <线程B instance = new Singleton(); } } } return instance; }} memory =allocate(); //1:分配对象的内存空间 instance =memory; //3:设置instance指向刚分配的内存地址 线程A> ctorInstance(memory); //2:初始化对象
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 <线程B synchronized (Singleton.class){ //同步锁 if (instance == null) { //双重检测机制 instance = new Singleton(); } } } return instance; <线程B (instance 为初始化) }} memory =allocate(); //1:分配对象的内存空间 instance =memory; //3:设置instance指向刚分配的内存地址 线程A> ctorInstance(memory); //2:初始化对象
如何避免这一情况呢?我们需要在instance对象前面增加一个修饰符volatile。
单例模式第三版:
public class Singleton {
private Singleton() {} //私有构造函数
private volatile static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (this){ //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.
用最简单的方式理解,volatile 修饰符阻止了变量访问前后的指令重拍,保证了指令的执行顺序
经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。
以上的方法还是有漏洞的,如果通过反射的方式,仍然可以构建多个实例对象
几点说明:
- volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。有关volatile的详细原理,
- 什么是单例设计模式? (转)
- 什么是单例设计模式?
- 漫画:什么是单例设计模式?
- [Day7]什么是单例模式,如何理解mvc设计模式
- 什么是单例模式
- 什么是单例模式??????????、
- 什么是单例模式?
- 设计模式(单例设计模式)
- 设计模式(单例设计模式)
- 设计模式(单例)
- 设计模式(单例)
- 设计模式(单例)
- 什么是单例模式(singleton)?
- 什么是单例模式(singleton)
- 什么是php单例模式?
- 设计模式之单例设计模式(饿汉单例设计模式&懒汉单例设计模式)
- (转)设计模式(1):单例模式
- 设计模式(二)单例模式(转)
- 什么是http,什么是tomcat,什么是Servlet
- 细说mysql索引
- Java线程池的基本原理
- 分布式开发--分布式内存管理
- 拓扑排序之邻接表实验
- 什么是单例设计模式? (转)
- 爱上WordPress系列教程:(一)WordPress环境准备与安装
- Service and Privacy Policy
- Verilog RTL 代码设计新手上路
- mysql timezone exception fix
- 激活函数-sigmoid
- 编写代码模拟手机与SIM卡组合的关系
- 控制跳转语句
- 我的算法学习之路1_算法作用