`

单例模式与双重检测(转载)

 
阅读更多
详见:http://www.iteye.com/topic/652440

首先要解释一下什么是延迟加载,延迟加载就是等到真真使用的时候才去创建实例,不用时不要去创建。



从速度和反应时间角度来讲,非延迟加载(又称饿汉式)好;从资源利用效率上说,延迟加载(又称懒汉式)好。



下面看看几种常见的单例的设计方式:



第一种:非延迟加载单例类



Java代码 
1.public class Singleton { 
2. private Singleton() {} 
3. private static final Singleton instance = new Singleton(); 
4. public static Singleton getInstance() { 
5.  return instance; 
6. } 
7.} 



第二种:同步延迟加载



Java代码 
1.public class Singleton { 
2. private static Singleton instance = null; 
3. private Singleton() {} 
4. public static synchronized Singleton getInstance() { 
5.  if (instance == null) { 
6.   instance = new Singleton(); 
7.  } 
8.  return instance; 
9. } 
10.} 



第三种:双重检测同步延迟加载
为处理原版非延迟加载方式瓶颈问题,我们需要对 instance 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。JDK5.0以后版本若instance为volatile则可行:



Java代码 
1.public class Singleton { 
2. private volatile static Singleton instance = null; 
3. private Singleton() {} 
4. public static Singleton getInstance() { 
5.  if (instance == null) { 
6.   synchronized (Singleton.class) {// 1 
7.    if (instance == null) {// 2 
8.     instance = new Singleton();// 3 
9.    } 
10.   } 
11.  } 
12.  return instance; 
13. } 
14.} 

双重检测锁定失败的问题并不归咎于 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(以jdk1.2开始)以前对于实例字段是直接在主储区读写的.所以当一个线程对resource进行分配空间,
初始化和调用构造方法时,可能在其它线程中分配空间动作可见了,而初始化和调用构造方法还没有完成.

但是从JAVA2以后,JMM发生了根本的改变,分配空间,初始化,调用构造方法只会在线程的工作存储区完成,在没有
向主存储区复制赋值时,其它线程绝对不可能见到这个过程.而这个字段复制到主存区的过程,更不会有分配空间后
没有初始化或没有调用构造方法的可能.在JAVA中,一切都是按引用的值复制的.向主存储区同步其实就是把线程工作
存储区的这个已经构造好的对象有压缩堆地址值COPY给主存储区的那个变量.这个过程对于其它线程,要么是resource
为null,要么是完整的对象.绝对不会把一个已经分配空间却没有构造好的对象让其它线程可见.



另一篇详细分析文章:http://www.iteye.com/topic/260515



第四种:使用ThreadLocal修复双重检测



借助于ThreadLocal,将临界资源(需要同步的资源)线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为了线程局部范围内来作。这里的ThreadLocal也只是用作标示而已,用来标示每个线程是否已访问过,如果访问过,则不再需要走同步块,这样就提高了一定的效率。但是ThreadLocal在1.4以前的版本都较慢,但这与volatile相比却是安全的。





Java代码 
1.public class Singleton { 
2. private static final ThreadLocal perThreadInstance = new ThreadLocal(); 
3. private static Singleton singleton ; 
4. private Singleton() {} 
5.  
6. public static Singleton  getInstance() { 
7.  if (perThreadInstance.get() == null){ 
8.   // 每个线程第一次都会调用 
9.   createInstance(); 
10.  } 
11.  return singleton; 
12. } 
13. 
14. private static  final void createInstance() { 
15.  synchronized (Singleton.class) { 
16.   if (singleton == null){ 
17.    singleton = new Singleton(); 
18.   } 
19.  } 
20.  perThreadInstance.set(perThreadInstance); 
21. } 
22.} 


第五种:使用内部类实现延迟加载
为了做到真真的延迟加载,双重检测在Java中是行不通的,所以只能借助于另一类的类加载加延迟加载:



Java代码 
1.public class Singleton { 
2. private Singleton() {} 
3. public static class Holder { 
4.  // 这里的私有没有什么意义 
5.  /* private */static Singleton instance = new Singleton(); 
6. } 
7. public static Singleton getInstance() { 
8.  // 外围类能直接访问内部类(不管是否是静态的)的私有变量 
9.  return Holder.instance; 
10. } 
11.} 



单例测试



下面是测试单例的框架,采用了类加载器与反射。
注,为了测试单便是否为真真的单例,我自己写了一个类加载器,且其父加载器设置为根加载器,这样确保Singleton由MyClassLoader加载,如果不设置为根加载器为父加载器,则默认为系统加载器,则Singleton会由系统加载器去加载,但这样我们无法卸载类加载器,如果加载Singleton的类加载器卸载不掉的话,那么第二次就不能重新加载Singleton的Class了,这样Class不能得加载则最终导致Singleton类中的静态变量重新初始化,这样就无法测试了。
下面测试类延迟加载的结果是可行的,同样也可用于其他单例的测试:



Java代码 
1.public class Singleton { 
2. private Singleton() {} 
3. 
4. public static class Holder { 
5.  // 这里的私有没有什么意义 
6.  /* private */static Singleton instance = new Singleton(); 
7. } 
8. 
9. public static Singleton getInstance() { 
10.  // 外围类能直接访问内部类(不管是否是静态的)的私有变量 
11.  return Holder.instance; 
12. } 
13.} 
14. 
15.class CreateThread extends Thread { 
16. Object singleton; 
17. ClassLoader cl; 
18. 
19. public CreateThread(ClassLoader cl) { 
20.  this.cl = cl; 
21. } 
22. 
23. public void run() { 
24.  Class c; 
25.  try { 
26.   c = cl.loadClass("Singleton"); 
27.   // 当两个不同命名空间内的类相互不可见时,可采用反射机制来访问对方实例的属性和方法 
28.   Method m = c.getMethod("getInstance", new Class[] {}); 
29.   // 调用静态方法时,传递的第一个参数为class对象 
30.   singleton = m.invoke(c, new Object[] {}); 
31.   c = null; 
32.   cl = null; 
33.  } catch (Exception e) { 
34.   e.printStackTrace(); 
35.  } 
36. } 
37.} 
38. 
39.class MyClassLoader extends ClassLoader { 
40. private String loadPath; 
41. MyClassLoader(ClassLoader cl) { 
42.  super(cl); 
43. } 
44. public void setPath(String path) { 
45.  this.loadPath = path; 
46. } 
47. protected Class findClass(String className) throws ClassNotFoundException { 
48.  FileInputStream fis = null; 
49.  byte[] data = null; 
50.  ByteArrayOutputStream baos = null; 
51. 
52.  try { 
53.   fis = new FileInputStream(new File(loadPath 
54.     + className.replaceAll("\\.", "\\\\") + ".class")); 
55.   baos = new ByteArrayOutputStream(); 
56.   int tmpByte = 0; 
57.   while ((tmpByte = fis.read()) != -1) { 
58.    baos.write(tmpByte); 
59.   } 
60.   data = baos.toByteArray(); 
61.  } catch (IOException e) { 
62.   throw new ClassNotFoundException("class is not found:" + className, 
63.     e); 
64.  } finally { 
65.   try { 
66.    if (fis != null) { 
67.     fis.close(); 
68.    } 
69.    if (fis != null) { 
70.     baos.close(); 
71.    } 
72. 
73.   } catch (Exception e) { 
74.    e.printStackTrace(); 
75.   } 
76.  } 
77.  return defineClass(className, data, 0, data.length); 
78. } 
79.} 
80. 
81.class SingleTest { 
82. public static void main(String[] args) throws Exception { 
83.  while (true) { 
84.   // 不能让系统加载器直接或间接的成为父加载器 
85.   MyClassLoader loader = new MyClassLoader(null); 
86.   loader 
87.     .setPath("D:\\HW\\XCALLC16B125SPC003_js\\uniportal\\service\\AAA\\bin\\"); 
88.   CreateThread ct1 = new CreateThread(loader); 
89.   CreateThread ct2 = new CreateThread(loader); 
90.   ct1.start(); 
91.   ct2.start(); 
92.   ct1.join(); 
93.   ct2.join(); 
94.   if (ct1.singleton != ct2.singleton) { 
95.    System.out.println(ct1.singleton + " " + ct2.singleton); 
96.   } 
97.   // System.out.println(ct1.singleton + " " + ct2.singleton); 
98.   ct1.singleton = null; 
99.   ct2.singleton = null; 
100.   Thread.yield(); 
101.  } 
102. } 
103.} 
分享到:
评论

相关推荐

    单例模式与双重检测

    下面将详细讨论单例模式的概念、实现方式以及双重检测机制。 首先,理解单例模式的基本概念。单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这样做的好处是避免了大量的实例化...

    43丨单例模式(下):如何设计实现一个集群环境下的分布式单例模式?1

    单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。在单例模式中,类的构造函数是私有的,防止外部直接创建对象,而是通过静态方法获取该类的唯一实例。单例模式的唯一性通常是在进程范围内,...

    设计模式单例模式和工厂模式综合应用

    "设计模式单例模式和工厂模式综合应用"的主题聚焦于两种常用的设计模式:单例模式和工厂模式,并探讨它们如何协同工作来实现高效、灵活的代码结构。这个主题尤其适用于Java编程语言,因为Java的面向对象特性使得设计...

    设计模式之单例模式(结合工厂模式)

    将单例模式与工厂模式结合,可以创建一个单例工厂,这个工厂类负责生成单例对象。这样做有两个主要好处:一是隐藏了单例的实现细节,使得代码更加整洁,降低了耦合度;二是可以通过工厂方法扩展新的实现,如果将来...

    C#单例模式详解 C#单例模式详解C#单例模式详解

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C#中,单例模式常用于管理共享资源或控制类的实例化过程,以提高性能、节约系统资源,特别是在整个应用程序生命周期内只需要一...

    java单例模式实例

    单例模式是软件设计模式中的一种经典模式,用于确保一个类只有一个实例,并提供一个全局访问点。在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们...

    使用C++11实现线程安全的单例模式

    在C++编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。线程安全的单例模式在多线程环境下尤其重要,因为不正确的实现可能导致多个线程创建多个实例,这违反了单例模式...

    设计模式——单例模式

    **设计模式——单例模式** 在软件工程中,设计模式是一种在特定场景下解决常见问题的标准方案,可以被复用并提升代码质量。单例模式是设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。这种模式...

    xfhy#Android-Notes#7.单例模式的双重检查锁为什么必须加volatile1

    这种写法可以保证线程安全.两个if都是不能去掉的.如果去掉第一个if: 那么所有的线程都会到这里来先获取锁,然后判断singleton是否为空.所有线程都会串行

    Java SE程序 单例模式

    Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式...

    单例模式详解~~单例模式详解~~

    单例模式是一种设计模式,它的主要目标是确保一个类只有一个实例,并提供一个全局访问点。在软件工程中,单例模式常用于控制资源的共享,比如数据库连接池、线程池或者日志系统等,这些资源通常需要全局唯一且高效地...

    7种单例模式

    单例模式是软件设计模式中的一种经典模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制共享资源、管理配置对象等。下面将详细介绍七种常见的单例模式实现...

    设计模式之单例模式源码demo

    单例模式是软件设计模式中的经典模式之一,其主要目的是控制类的实例化过程,确保在应用...在设计模式中,单例模式不仅限于上述实现,还可以与其他模式结合使用,如工厂模式、装饰器模式等,以达到更灵活、高效的设计。

    单例模式和工厂模式代码

    单例模式和工厂模式是两种常见的软件设计模式,在面向对象编程中扮演着重要的角色。它们都是为了解决特定的问题而提出的解决方案,但有着不同的应用场景和设计思路。 **单例模式** 是一种限制类实例化次数的模式,...

    设计模式 中的 单例模式和观察者模式

    在Java中,可以使用双重检查锁定(Double-Check Locking)来实现线程安全的单例: ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static ...

    实验12 单例模式与枚举.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java 单例模式 工具类

    Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...

    几种单例模式demo

    单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下都非常有用,比如控制资源的唯一性、管理共享配置或者创建昂贵的对象时避免频繁创建销毁。 ...

    php单例模式和工厂模式

    工厂模式则关注于对象的创建,提供一个统一的入口,使得对象的创建与使用解耦。两者都是设计模式中的重要概念,能够提高代码质量并降低维护成本。在实际项目中,根据需求选择合适的模式能有效提升软件架构的灵活性...

Global site tag (gtag.js) - Google Analytics