定义html
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”java
Java中单例模式定义:“一个类有且仅有一个实例,而且自行实例化向整个系统提供。”spring
主要解决:一个全局使用的类频繁地建立与销毁。设计模式
什么时候使用:当您想控制实例数目,节省系统资源的时候。安全
特色框架
A.这些类只能有一个实例;jvm
B.可以自动实例化;函数
C.这个类对整个系统可见,即必须向整个系统提供这个实例。优化
优势.net
实例
实现方式
单例模式的实现一般有两种方式:“饿汉式”和“懒汉式”。
单例模式是将将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的惟一实例只能经过提供的入口得到[例如getInstance()方法], 事实上,经过Java反射机制是可以实例化构造方法为private的类的,那基本上会使全部的Java单例实现失效,咱们不考虑java反射机制
懒汉式
懒汉式在类建立的时候不会去建立实例,在第一次调用时才会去建立,可是会有线程安全问题
饿汉式
在类建立的时候建立出实例,这样只会有一个实例被建立,没有线程安全不安全的问题
单例模式初版
public class Singleton { private Singleton() {} //私有构造函数 private static Singleton instance = null; //单例对象 // 静态工厂方法 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方法:
由于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对象。
3.若是已经存在该对象的实例,再次进行访问时不用再对对象进行加锁操做。
像这样两次判空的机制叫作双重检测机制。
假设这样的场景,当两个线程一先一后访问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对象。以下图所示:
如何避免这一状况呢?咱们须要在instance对象前面增长一个修饰符volatile。
单例模式第三版:
public class Singleton { private Singleton() { } //私有构造函数 private volatile static Singleton instance = null; //单例对象 //静态工厂方法 public static Singleton getInstance() { if (instance == null) { //双重检测机制 synchronized (Singleton.class) { //同步锁 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的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。
1. volatile关键字不但能够防止指令重排,也能够保证线程访问的变量值是主内存中的最新值。有关volatile的详细原理,自行百科。
用静态内部类实现单例模式:
public class Singleton{ private static class LazyHolder{ private static final Singleton INSTANCE= newSingleton(); } private Singleton(){} public static Singleton getInstance(){ returnLazyHolder.INSTANCE; } }
这里有几个须要注意的点:
1.从外部没法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能获得单例对象INSTANCE。
2.INSTANCE对象初始化的时机并非在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。所以这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全
总结:
单例模式是为了实现整个系统内部在运行过程当中只有一个实例,(饿汉 懒汉 线程安全 双重判空 volidate防止jvm指令重排)
转自: http://www.sohu.com/a/206960903_479559
单例模式其余的实现方式,能够参考:http://www.runoob.com/design-pattern/singleton-pattern.html
也能够经过http://blog.csdn.net/jason0539/article/details/23297037/等博文,加深本身对单例模式的理解