首先要解释一下什么是延迟加载,延迟加载就是等到真真使用的时候才去创建实例,不用时不要去创建。 从速度和反应时间角度来讲,非延迟加载(又称饿汉式)好;从资源利用效率上说,延迟加载(又称懒汉式)好。 下面看看几种常见的单例的设计方式: 第一种:非延迟加载单例类
public class Singleton { private Singleton() {} private static final Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } } 第二种:同步延迟加载
public class Singleton { private static Singleton instance = null; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
第三种:双重检测同步延迟加载
public class Singleton { private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {// 1 if (instance == null) {// 2 instance = new Singleton();// 3 } } } return instance; } } 双重检测锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是失败的一个主要原因。 无序写入:
为解释该问题,需要重新考察上述清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象,这样会导致系统崩溃。 什么?这一说法可能让您始料未及,但事实确实如此。在解释这个现象如何发生前,请先暂时接受这一事实,我们先来考察一下双重检查锁定是如何被破坏的。假设代码执行以下事件序列: 1、线程 1 进入 getInstance() 方法。 2、由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。 3、线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。 4、线程 1 被线程 2 预占。 5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。 6、线程 2 被线程 1 预占。 7、线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。 为展示此事件的发生情况,假设代码行 instance =new Singleton(); 执行了下列伪代码:
mem = allocate(); //为单例对象分配内存空间. instance = mem; //注意,instance 引用现在是非空,但还未初始化 ctorSingleton(instance); //为单例对象通过instance调用构造函数 这段伪代码不仅是可能的,而且是一些 JIT 编译器上真实发生的。执行的顺序是颠倒的,但鉴于当前的内存模型,这也是允许发生的。JIT 编译器的这一行为使双重检查锁定的问题只不过是一次学术实践而已。
如果真像这篇文章:http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html所说那样的话,1.2或以后的版本就不会有问题了,但这个规则是JMM的规范吗?谁能够确认一下。 但是从JAVA2以后,JMM发生了根本的改变,分配空间,初始化,调用构造方法只会在线程的工作存储区完成,在没有 另一篇详细分析文章:http://www./topic/260515 第四种:使用ThreadLocal修复双重检测 借助于ThreadLocal,将临界资源(需要同步的资源)线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为了线程局部范围内来作。这里的ThreadLocal也只是用作标示而已,用来标示每个线程是否已访问过,如果访问过,则不再需要走同步块,这样就提高了一定的效率。但是ThreadLocal在1.4以前的版本都较慢,但这与volatile相比却是安全的。
public class Singleton { private static final ThreadLocal perThreadInstance = new ThreadLocal(); private static Singleton singleton ; private Singleton() {} public static Singleton getInstance() { if (perThreadInstance.get() == null){ // 每个线程第一次都会调用 createInstance(); } return singleton; } private static final void createInstance() { synchronized (Singleton.class) { if (singleton == null){ singleton = new Singleton(); } } perThreadInstance.set(perThreadInstance); } }
public class Singleton { private Singleton() {} public static class Holder { // 这里的私有没有什么意义 /* private */static Singleton instance = new Singleton(); } public static Singleton getInstance() { // 外围类能直接访问内部类(不管是否是静态的)的私有变量 return Holder.instance; } } 单例测试
下面是测试单例的框架,采用了类加载器与反射。
|
|