您好,欢迎光临本网站![请登录][注册会员]  
文件名称: 单例模式七种写法_转
  所属分类: 其它
  开发工具:
  文件大小: 209kb
  下载次数: 0
  上传时间: 2019-03-23
  提 供 者: weixin_********
 详细说明:NULL 博文链接:https://kaka100.iteye.com/blog/1060519在早期,代价相当高。随着吏高级的JM的出现,同步的代价降低了,但出入 synchronized方 法或块仍然有性能损失。不考虑JVM技术的进步,程序员们绝不想不必要地浪费处理时间。 因为只有清单2中的2行需要同步,我们可以只将其包装到一个同步块中,如清单3所示: 清单3. getInstance0方法 public static Singleton getinstanceO if (instance synchronized(Singleton class)i instance= new Singleton( cturn instance 清单3中的代码展示了用多线程加以说明的和清单1相同的问题。当 instance为null时 两个线程可以并发地进入if语句内部。然后,一个线程进入 synchronized块来初始化 instance,而另一个线程则被阻断。当第一个线程退出 synchronized块时,等待着的线程进入并 创建另一个 Singleton对象。注意:当第一个线程进入 synchronized块时,它并没有检查 Instance是否非null 回页首 双重检查锁定 为处理清单3中的问题,我们需要对 instance进行第二次检查。这就是“双重检查锁定”名称 的由来。将双重检查锁定习语应用到清单3的结果就是清单4。 清单4.双重检查锁定示例 public static Singleton getInstanceO if (instance ==null) synchronized(Singleton class)i //1 if (instance==null instance=new Singleton(; //3 return instance 双重检查锁定背后的理论是:在〃2处的第二次检査使(如清单3中那样)创建两个不同的 Singleton对象成为不可能。假设有下列∮件序列: 9线程1进入 gctInstancc()方法。 10由于 Instance为null,线程1在m处进入 synchronized块。 1线程1被线程2预占。 12线程2进入 getlnstancer()方法。 13由于 Instance仍旧为nul,线程2试图获取∥l处的锁。然而,由于线程I持有该 锁,线程2在1处阻塞。 14线程2被线程1预占 15线程执行,由于在∥2处实例仍旧为null,线程|还创建一个 Singleton对象并将 其引用赋值给 instance 16线程1退出 synchronized块并从 getInstanceo方法返回实例。 17线程1被线程2预占。 18线程2获取1处的锁并检查 instancy是否为null I9由于 Instance是非mull的,并没有创建第二个 Singleton对象,由线程1创建的对象 被返回。 双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双里检查锁定的问题是:并不 能保证它会在单处理器或多处理器计算机上顺利运行。 双重检查锁定失败的问题并不归咎于JM中的实现bug,而是归咎于Java平台内存模型 内存模型允许所谓的“无序写入”,这也是这些习语失败的个主要原因。 回页首 无序写入 为解释该问题,需要重新考察上述清单4中的/3行。此行代码创建∫一个 Singleton对象并 初始化变量 instance来引用此对象。这行代码的问题是:在 Singleton构造函数体行之前, 变量 Instance可能成为非mul的。 什么?这一说法可能让您始料未及,但事实确实如此。在解释这个现象如何发生前,请先暂时 接受这一事实,我们先来考察一下双重检查锁定是如何被破坏的。假设清单4中代码执行以下 事件序列: 20线程1进入 getlnstancer()方法。 21由于 Instance为nul,线程1在∥处进入 synchronized块。 22线程1前进到/3处,但在构造函数执行之前,使实例成为非nul 23线程Ⅰ被线程2预占。 24线程2检查实例是否为null因为实例不为null,线程2将 instance引用返回给一 个构造完整但部分初始化」的 Singleton对象 25线程2被线程1预占。 26线程1通过运行 Singleton对象的构造函数并将引用返回给它,米完成对该对象的初 始化。 此事件序列发生在线程2返回一个尚未执行构造函数的对象的时候。 为展示此事件的发生情况,假设为代码行 instance= new singleton();执行了下列伪代码: instance -new Singleton( nem=allocate Allocate memory for Singleton object instance men //Note that instance is now non-null, but Zhas not been initialized ctorSingleton(instance) /Invoke constructor for Singleton passing 这段伪代码不仅是可能的,而且是些JII编译器上真实发生的。执行的顺序是颠倒的,但鉴 于当前的內冇模型,这也是允许发生的。JT编译器的这一行为使双重检查锁定的问题只不过 是次学术实践而已 为说明这一情况,假设有清单5中的代码。它包含一个剥离版的 getlnstance0方法。我已经 删除了“双重检查性”以简化我们刈生成的汇编代码(清单6)的回顾。我们只关心JIT编译器 如何编译 instance- new singleton(;代码。此外,我提供了一个简单的构造函数来明确说明汇编 代码中该构造函数的运行情况。 清单5用于演示无序写入的单例类 class singleton private static Singleton instance private boolean in Use pI rivate int va private Singleton( Use= true val=5 public static Singleton getlnstanceo) if (instance ==nul instance=new Singleton return instance 清单6包含由 Sun jDK1.2.1JI编译器为清单5中的 gctInstanccO方法体生成的汇编代 码 清单6.由清单5中的代码生成的汇编代码 asm code generated for getInstance 054D20B0 moy eax,[049388C8 load instance ref 054D20B5 test eax. eax test for null 054D20B7 ne 054D20D7 054D20B9mov eax. 14c0988h 054D20BE call 503EF8F0 c allocate memory 054D20C3 mov 049388C8],eax Store pointer in instance rcf instance non-null and ctor as not run 054D20C8 mov ecx, dword ptr [eax 054D20C∧mov dword ptr [],I inline ctor-inUse=true 054D20D0 mov dword ptr [ecx+4 1, 5 inline ctor-val-=5 054D20D7 mov bx, dword ptr ds: [49388C8h 054D20DD mp 054D20B0 注:为引用下列说明中的汇编代码行,我将引用指令地址的最后两个值,因为它们都以054D20 开头。例如,B5代表 test eax,eax 江编代码是通过运行一个在无限循环屮调用 getInstance0方法的测试程序来生成的。程序运彳 时,请运行 Microsoft visual c++调试器并将其附到表示测试程序的Java进程中。然后,中断 执行并找到表示该无限循环的汇编代码。 B0和B5处的前两行汇编代码将 Instance引用从内存位置049388℃8加载至eax中,并进 行null检查。这跟清单5中的 getInstance(方法的第一行代码相刈应。第一次调用此方法时, Instance为nul,代码执行到B9。BE处的代码为 Singleton对象从堆中分配内存,并将一个 指向该块内冇的指针存储到eax中。下一行代码,C3,获取eax屮的指针并将其行储回内存 位置为049388c8的实例引用。结果是, instance现在为非nl并引用一个有效的 Singleton 对象。然而,此对象的构造函数尚未运行,这恰是破坏双重检査锁定的情况。然后,在C8行 处, Instance指针被解除引用并存储到ecx。CA和D0行表示内联的构造凶数,该构造函数 将值true和5存储到 Singleton对象。如果此代码在执行C3行后且在完成该构造函数前被 另一个线程中断,则双重检查锁定就会失败。 不是所有的JIT编译器都生成如上代码。一些生成了代码,从而只在构造函数执行后使 instance 成为非null针对Java技术的 IBM SDK13版和 Sun jdK1.3都生成这样的代码。然而, 这并不意味着应该在这些实例中使用双重检査锁定。该习语失败还有一些其他原因。此外,您 并不总能知道代码会在哪些JM上运行,而JT编译器总是会发生变化,从而生成破坏此习 语的代码。 回页首 双重检查锁定:获取两个 考虑到当前的双重检查锁定不起作用,我加入∫另一个版本的代码,如清单7所示,从而防止 您刚才看到的无序写入问题。 清单7.解决无序写入问题的尝试 public static Singleton getlnstanceO if (instance=null) synchronized(Singleton class)i /1 Singleton inst -instance if (inst== null synchronized( Singleton class)i /3 inst-new singleton(; /4 stance= inst /5 return instance: 看着淸单7中的代码,您应该意识到事情变得有点荒谬。请记住,创建双重检査锁定是为了避 免对简单的三行 getlnstance(方法实现冋步。清单7屮的代码变得难于控制。另外,该代码 没有解决问题。仔细检查可获悉原因。 此代码试图避免无序写入问题。它试图通过引入局部变量inst和第二个 synchronized块来解 决这一问题。该理论实现如下: 27线程1进入 getlnstance方法 28由于 Instance为ndl,线程1在处进入第个 synchronized块 29局部变量inst获取 instance的值,该值在∥2处为null 30由于inst为null线程1在3处进入第二个 synchronized块。 31线程1然后开始执行/4处的代码,同吋使inst为非nul,但在 Singleton的构造函 数执行前。(这就是我们刚才看到的无序写入问题。) 32线程1被线程2预占。 33线程2进入 getlnstanceo方法。 34由于 instance为null线程2试图在/1处进入第一个 synchronized块。由于线程1 目前持有此锁,线程2被阻断。 35线程1然后完成/4处的执行 36线程1然后将一个构造完整的 Singleton对象在〃5处赋值给变量 instance,并退出这 两个 synchronized块 37线程1返回 Instance 38然后执行线程2并在〃2处将 instance赋值给 inst 39线程2发现 instance为非nul,将其返回。 这甲的关键行是/5。此行应该确保 Instance只为null或引用一个构造完整的 Singleton对 象。该问题发生在理论和实际彼此背道而馳的情况下。 由于当前内存模型的定义,清单7中的代码无效。Java语言规范( Java Language Specification JLS)要求不能将 synchronized块中的代码移出来。但是,并没有说不能将 synchronized块外 面的代码移入 synchronized块中。 JII编译器会住这里看到个优化的机会。此优化会朋除14和/5处的代码,组合并且生成 清单8中所示的代码 清单8.从清单7中优化来的代码 public static Singleton gctInstanccO if (instance synchronized (Singletonclass)i 1 Singleton inst= instance 2 if(inst-=null) synchronized(singleton class) /3 ingleton() instance =new Singleton /instance= inst 5 return instance 如果进行此项优化,您将同样遇到我们之前讨论过的无序写入问题 回页首 用 volatile声明每一个变量怎么样? 另个想法是针对变量inst以及 instance使用关键字 volatile。根据JS(参见参考资料) 声明成 volatile的变量被认为是顺序一致的,即,不是重新排序的。但是试图使用 volatile来 修止双重检查锁定的问题,公产生以下两个问题: 这里的问题不是有关顺序一致性的,而是代码被移动了,不是重新排序。 即使考虑∫顺序·致性,大多数的JWM也没有正确地实现 volatile 第二点值得展开讨论。假设有清单9屮的代码: 清单9.使用了 volatile的顺序一致性 class test private volatile boolean stop- false private volatile int num=0 public void fool num=100; //This can happen second stop= true; //This can happen first public void barO num +=num: //num can==0 根据JS,由于stop和num被声明为 volatile,它们应该顺序一致。这意味着如果stop曾 经是truc,num定曾被设置成100。尽管如此,因为许多JM没有实现 volatile的顺序 致性功能,您就不能依赖此行为。因此,如果线程1调用foo并且线程2并发地调用bar, 则线程1可能在num被设置成为100之前将sop设置成true。这将导致线程见到sop是 true,前numn仍被设置成0。使用 volatile和64位变量的原子数述有另外一些问题,但这已 超出了本文的讨论范围。有关此主题的更多信息,请参阅参考资料。 回页首 解决方案 底线就是:无论以何种形式,都不应使用双重检查锁定,因为您不能保证它在任何JM实现 上都能顺利运行。JSR-13是有关内存模型寻址问题的,尽管如此,新的内存模型也不会支持 双重检查锁定。因此,您有两种选择: 接受如清单2中所示的 getInstance(方法的同步。 放弃同步,而使用个stic字段 选择项2如清单10屮所示 清单10.使用 static字段的单例实现 private Vector v private boolean inUse private static Singleton instance =new Singleton( private Singleton( v= new Vector nUse true public static Singleton getlnstanceO return instance 清单10的代码没有使用同步,并且确保调用 static gctInstanccO)方法时才创建 Singleton。如 果您的目标是消除同步,则这将是一个很好的选择。 回贞首 String不是不变的 蓥于无序写入和引用在构造函数执行前变成非mull的问题,您可能会考虑 String类。假设有 下列代码 private String str; str=new String (" hello") String类应该是不变的。尽管如此,鉴于我们之前讨论的无序写入问题,那会在这里导致问题 吗?答案是肯定的。考處两个线程访问 String str。一个线程能看见sr引用一个 String对象, 在该对象中构造函数尚未运行。事实上,清单I1包含展示这种情况发生的代码。注意,这个 代码仅在我测试用的旧版JVM上会失败。IBM13和Sun1.3JVM都会如期生成不变的 清单1l可变 String的例子 class string Creator extends thread Mutable String ms public String Creator(MutableString muts d public void rung whilc(truc) msstr=new String("hello") lass String Reader extends Thread Mutable String ms public StringRe ableString muts ms=muts, public void runo hile( true) if( (ms str equals(" hello"))) Systcm.out. printIn("'String is not immutable! " brcak
(系统自动生成,下载前可以参看下载内容)

下载文件列表

相关说明

  • 本站资源为会员上传分享交流与学习,如有侵犯您的权益,请联系我们删除.
  • 本站是交换下载平台,提供交流渠道,下载内容来自于网络,除下载问题外,其它问题请自行百度
  • 本站已设置防盗链,请勿用迅雷、QQ旋风等多线程下载软件下载资源,下载后用WinRAR最新版进行解压.
  • 如果您发现内容无法下载,请稍后再次尝试;或者到消费记录里找到下载记录反馈给我们.
  • 下载后发现下载的内容跟说明不相乎,请到消费记录里找到下载记录反馈给我们,经确认后退回积分.
  • 如下载前有疑问,可以通过点击"提供者"的名字,查看对方的联系方式,联系对方咨询.
 输入关键字,在本站1000多万海量源码库中尽情搜索: