单例模式 html
单例模式是一种比较常见的模式,看起来很简单,可是要作到高效安全的使用,其中仍是有不少要点的。参考了Head First及众多网友的文章,稍微总结一下,以备查看。安全
单例模式的定义:确保一个类只有一个实例,而且提供一个全局访问点。多线程
1. 最简单的单例(饿汉模式),程序一加载就对 instance 变量进行初始化,好处是简单明了,缺点:若是初始化的耗费的资源特别多,而以后这个类可能未被使用到,就会形成浪费。并发
public class Singleton{ // 单例类静态引用 private static Singleton instance = new Singleton(); // 私有构造方法 private Singleton(){} // 静态方法:全局访问点 public static Singleton getInstance(){ return instance; } }
2. 延迟加载(懒汉模式),做为改进,咱们能够在使用到的时候才去建立单例类。性能
public class LazySingleton { private static LazySingleton instance = null; private LazySingleton() {} public static LazySingleton getInstance() { // 使用的时候去检测是否已经初始化 if (instance == null) { instance = new LazySingleton(); } return instance; } }
这种方式实现的单例是线程非安全的,假设有线程 A 和 B,同时去获取 LazySingleton 实例,当 A 线程进入 getInstance(),执行到 if (instance == null) 时,此时判断结果为真;以后切换到线程 B 执行,此时也执行到 if (instance == null) ,此时判断结果为真,会对 instance 变量进行初始化;此时切换回线程 A ,根据以前的判断结果,也会对 instance 变量进行初始化,此时就会获得两个不同的实例,单例失败。学习
为了实现线程安全,只要在 getInstance() 方法上添加 synchronized 关键字便可。优化
3. 双重校验锁this
加上 synchronized 关键字,实现了线程安全,但又带来了性能问题。经过同步方法会让性能大幅降低。spa
经过代码咱们能够看到,instance 变量在整个程序执行期间只会初始化一次,若是只为了这一次初始化,每次获取单例对象都必须在synchronized这里进行排队,实在太损耗性能。为此咱们能够以下改进:① instance 变量只会初始化一次,把 synchronized 关键字加载此处;② 为了不每次获取单例对象都在同步代码上等待,能够在同步代码块外层再加一次 instance == null 判断。线程
public class DoubleCheckSingleton { private static DoubleCheckSingleton instance = null; private DoubleCheckSingleton() {} public static DoubleCheckSingleton getInstance() { // 两层校验才能确保单例 if (instance == null) { synchronized (DoubleCheckSingleton.class) { if (instance == null) { instance = new DoubleCheckSingleton(); } } } return instance; } }
4. volatile 关键字
Java中的指令重排优化是指在不改变原语义(即保证运行结果不变)的状况下,经过调整指令的执行顺序让程序运行的更快。JVM中并无规定编译器优化相关的内容,也就是说JVM能够自由的进行指令重排序的优化。这种优化在单线程中是没有任何问题的,可是在多线程中就会出现问题。
建立一个变量须要2个步骤:一个是申请一块内存,调用构造方法进行初始化操做;另外一个是分配一个指针指向这块内存。这两个操做谁在前谁在后呢?JVM规范并无规定。那么就存在这么一种状况,JVM是先开辟出一块内存,而后把指针指向这块内存,最后调用构造方法进行初始化。假设线程A开始建立 SingletonClass 的实例,此时线程 B 调用了 getInstance() 方法,首先判断 instance 是否为 null。按照咱们上面所说的内存模型,A 已经把 instance 指向了那块内存,只是尚未调用构造方法,所以 B 检测到 instance 不为 null,因而直接把 instance 返回了——问题出现了,尽管instance不为null,但它并无构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,由于里面尚未收拾。此时,若是B在A将instance构造完成以前就是用了这个实例,程序就会出现错误了!
在 JDK1.5 及以后版本赋予了volatile关键字明确用法。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已是初始化过的,从而避免了上面说到的问题。因此只须要在 instance 变量前加上 volatile 修饰符便可避免Java指令优化带来的问题。
public class DoubleCheckSingleton { private static volatile DoubleCheckSingleton instance = null; private DoubleCheckSingleton() {} public static DoubleCheckSingleton getInstance() { // 两层校验才能确保单例 if (instance == null) { synchronized (DoubleCheckSingleton.class) { if (instance == null) { instance = new DoubleCheckSingleton(); } } } return instance; } }
注:volatile 关键字用法
① 可见性。可见性指的是在一个线程中对该变量的修改会立刻由工做内存(Work Memory)写回主内存(Main Memory),因此会立刻反应在其它线程的读取操做中(工做内存是线程独享的,主存是线程共享的)
② 禁止指令重排序优化。
5. 静态内部类
上述 volatile 关键字收到 JDK 版本限制,以下的静态内部类实现方式是确保了延迟加载和线程安全的。
public class StaticInnerSingleton { private static class Holder { private static StaticInnerSingleton instance = new StaticInnerSingleton(); } private StaticInnerSingleton() { } public static StaticInnerSingleton getSingleton() { return Holder.instance; } }
能够把 StaticInnerSingleton 实例放到一个静态内部类中,这样就避免了静态实例在 StaticInnerSingleton 类加载的时候就建立对象,而且因为静态内部类只会被加载一次,而类的构造必须是原子性的,非并发的,因此这种写法也是线程安全的
在上述代码中,由于 StaticInnerSingleton 没有 static 修饰的属性,所以并不会被初始化。直到调用 getInstance() 的时候,会首先加载 Holder
类,这个类有一个 static 的 StaticInnerSingleton 实例,所以须要调用 StaticInnerSingleton 的构造方法,而后 getInstance() 将把这个内部类的instance 返回给使用者。因为这个 instance 是 static 的,所以并不会构造屡次。
6. 枚举式单例
public enum EnumSingleton { INSTANCE; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSomething() { return "String"; } }
使用枚举除了线程安全和防止反射调用构造器以外,还提供了自动序列化机制,防止反序列化的时候建立新的对象。所以,《Effective Java》书中推荐使用的方法。
参考: 深刻Java单例模式 http://devbean.blog.51cto.com/448512/203501/
你真的会写单例模式吗--Java实现 http://www.importnew.com/18872.html
感谢二位做者文章,本文仅做整理学习,侵删!