ImageVerifierCode 换一换
格式:DOCX , 页数:27 ,大小:28.62KB ,
资源ID:1335186      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-1335186.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(双重检查锁定及单例模式Word文档下载推荐.docx)为本站会员(b****2)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

双重检查锁定及单例模式Word文档下载推荐.docx

1、 public static Singleton getInstance() if (instance = null) /1 instance = new Singleton(); /2 return instance; /3此类的设计确保只创建一个Singleton对象。构造函数被声明为private,getInstance()方法只创建一个对象。这个实现适合于单线程程序。然而,当引入多线程时,就必须通过同步来保护getInstance()方法。如果不保护getInstance()方法,则可能返回对象的两个不同的实例。假设两个线程并发调用方法并且按以下顺序执行调用:1.线程 1 调用方法并决

2、定instance在 /1 处为null。2.线程 1 进入if代码块,但在执行 /2 处的代码行时被线程 2 预占。3.线程 2 调用方法并在 /1 处决定为4.线程 2 进入代码块并创建一个新的对象并在 /2 处将变量分配给这个新对象。5.线程 2 在 /3 处返回对象引用。6.线程 2 被线程 1 预占。7.线程 1 在它停止的地方启动,并执行 /2 代码行,这导致创建另一个8.线程 1 在 /3 处返回这个对象。结果是方法创建了两个对象,而它本该只创建一个对象。通过同步方法从而在同一时间只允许一个线程执行代码,这个问题得以改正,如清单 2 所示:清单 2. 线程安全的 getInsta

3、nce() 方法public static synchronized Singleton getInstance()清单 2 中的代码针对多线程访问方法运行得很好。然而,当分析这段代码时,您会意识到只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 /2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定是非null的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是synchronized的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。为使此方法更为有效,一个被称为双重检查锁定的习语就应运而

4、生了。这个想法是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价。同步的代价在不同的 JVM 间是不同的。在早期,代价相当高。随着更高级的 JVM 的出现,同步的代价降低了,但出入方法或块仍然有性能损失。不考虑 JVM 技术的进步,程序员们绝不想不必要地浪费处理时间。因为只有清单 2 中的 /2 行需要同步,我们可以只将其包装到一个同步块中,如清单 3 所示:清单 3. getInstance() 方法public static Singleton getInstance() if (instance = null) synchronized(Singleton.class) 清单 3

5、中的代码展示了用多线程加以说明的和清单 1 相同的问题。当时,两个线程可以并发地进入语句内部。然后,一个线程进入块来初始化instance,而另一个线程则被阻断。当第一个线程退出块时,等待着的线程进入并创建另一个注意:当第二个线程进入块时,它并没有检查是否非回页首双重检查锁定为处理清单 3 中的问题,我们需要对进行第二次检查。这就是“双重检查锁定”名称的由来。将双重检查锁定习语应用到清单 3 的结果就是清单 4 。清单 4. 双重检查锁定示例 synchronized(Singleton.class) /1 if (instance = null) /2双重检查锁定背后的理论是:在 /2 处的

6、第二次检查使(如清单 3 中那样)创建两个不同的对象成为不可能。假设有下列事件序列:1.线程 1 进入方法。2.由于null,线程 1 在 /1 处进入块。3.线程 1 被线程 2 预占。5.由于仍旧为null,线程 2 试图获取 /1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 /1 处阻塞。7.线程 1 执行,由于在 /2 处实例仍旧为null,线程 1 还创建一个对象并将其引用赋值给instance。8.线程 1 退出块并从方法返回实例。9.线程 1 被线程 2 预占。10.线程 2 获取 /1 处的锁并检查是否为11.由于的,并没有创建第二个对象,由线程 1 创建的对象被返回。

7、双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。无序写入为解释该问题,需要重新考察上述清单 4 中的 /3 行。此行代码创建了一个对象并初始化变量来引用此对象。这行代码的问题是:在构造函数体执行之前,变量instance可能成为非的。什么?这一说法可能让您始料未及,但事实确实如此。在解释这个现象如何发生前,请先暂时接受这一事实,我们先来考察一下双重检查锁

8、定是如何被破坏的。假设清单 4 中代码执行以下事件序列:3.线程 1 前进到 /3 处,但在构造函数执行之前,使实例成为非4.线程 1 被线程 2 预占。5.线程 2 检查实例是否为因为实例不为 null,线程 2 将引用返回给一个构造完整但部分初始化了的7.线程 1 通过运行对象的构造函数并将引用返回给它,来完成对该对象的初始化。此事件序列发生在线程 2 返回一个尚未执行构造函数的对象的时候。为展示此事件的发生情况,假设为代码行instance =new Singleton();执行了下列伪代码:mem = allocate(); /Allocate memory for Singleton

9、 object.instance = mem; /Note that instance is now non-null, but /has not been initialized.ctorSingleton(instance); /Invoke constructor for Singleton passing /instance.这段伪代码不仅是可能的,而且是一些 JIT 编译器上真实发生的。执行的顺序是颠倒的,但鉴于当前的内存模型,这也是允许发生的。JIT 编译器的这一行为使双重检查锁定的问题只不过是一次学术实践而已。为说明这一情况,假设有清单 5 中的代码。它包含一个剥离版的我已经删除

10、了“双重检查性”以简化我们对生成的汇编代码(清单 6)的回顾。我们只关心 JIT 编译器如何编译instance=new Singleton();代码。此外,我提供了一个简单的构造函数来明确说明汇编代码中该构造函数的运行情况。清单 5. 用于演示无序写入的单例类 private int val; val = 5;清单 6 包含由 Sun JDK 1.2.1 JIT 编译器为清单 5 中的方法体生成的汇编代码。清单 6. 由清单 5 中的代码生成的汇编代码;asm code generated for getInstance054D20B0 mov eax,049388C8 ;load inst

11、ance ref054D20B5 test eax,eax ;test for null054D20B7 jne 054D20D7054D20B9 mov eax,14C0988h054D20BE call 503EF8F0 ;allocate memory054D20C3 mov 049388C8,eax ;store pointer in ;instance ref. instance non-null and ctorhas not run054D20C8 mov ecx,dword ptr eax 054D20CA mov dword ptr ecx,1 ;inline ctor -

12、inUse=true;054D20D0 mov dword ptr ecx+4,5 ;inline ctor - val=5;054D20D7 mov ebx,dword ptr ds:49388C8h054D20DD jmp 054D20B0注:为引用下列说明中的汇编代码行,我将引用指令地址的最后两个值,因为它们都以054D20开头。例如,B5代表test eax,eax。汇编代码是通过运行一个在无限循环中调用方法的测试程序来生成的。程序运行时,请运行 Microsoft Visual C+ 调试器并将其附到表示测试程序的 Java 进程中。然后,中断执行并找到表示该无限循环的汇编代码。B0

13、和B5处的前两行汇编代码将引用从内存位置049388C8加载至eax中,并进行检查。这跟清单 5 中的方法的第一行代码相对应。第一次调用此方法时,instance为null,代码执行到B9。BE处的代码为对象从堆中分配内存,并将一个指向该块内存的指针存储到中。下一行代码,C3,获取中的指针并将其存储回内存位置为的实例引用。结果是,instance现在为非并引用一个有效的然而,此对象的构造函数尚未运行,这恰是破坏双重检查锁定的情况。然后,在C8行处,instance指针被解除引用并存储到ecx。CA和D0行表示内联的构造函数,该构造函数将值true5存储到如果此代码在执行C3行后且在完成该构造函

14、数前被另一个线程中断,则双重检查锁定就会失败。不是所有的 JIT 编译器都生成如上代码。一些生成了代码,从而只在构造函数执行后使成为非针对 Java 技术的 IBM SDK 1.3 版和 Sun JDK 1.3 都生成这样的代码。然而,这并不意味着应该在这些实例中使用双重检查锁定。该习语失败还有一些其他原因。此外,您并不总能知道代码会在哪些 JVM 上运行,而 JIT 编译器总是会发生变化,从而生成破坏此习语的代码。双重检查锁定:获取两个考虑到当前的双重检查锁定不起作用,我加入了另一个版本的代码,如清单 7 所示,从而防止您刚才看到的无序写入问题。清单 7. 解决无序写入问题的尝试 Singl

15、eton inst = instance; if (inst = null) synchronized(Singleton.class) /3 inst = new Singleton(); /4 instance = inst; /5看着清单 7 中的代码,您应该意识到事情变得有点荒谬。请记住,创建双重检查锁定是为了避免对简单的三行方法实现同步。清单 7 中的代码变得难于控制。另外,该代码没有解决问题。仔细检查可获悉原因。此代码试图避免无序写入问题。它试图通过引入局部变量inst和第二个块来解决这一问题。该理论实现如下:null,线程 1 在 /1 处进入第一个3.局部变量获取的值,该值在

16、/2 处为4.由于null,线程 1 在 /3 处进入第二个5.线程 1 然后开始执行 /4 处的代码,同时使为非null,但在的构造函数执行前。(这就是我们刚才看到的无序写入问题。)6.线程 1 被线程 2 预占。7.线程 2 进入8.由于null,线程 2 试图在 /1 处进入第一个由于线程 1 目前持有此锁,线程 2 被阻断。9.线程 1 然后完成 /4 处的执行。10.线程 1 然后将一个构造完整的对象在 /5 处赋值给变量instance,并退出这两个11.线程 1 返回12.然后执行线程 2 并在 /2 处将赋值给inst。13.线程 2 发现null,将其返回。这里的关键行是 /

17、5。此行应该确保只为或引用一个构造完整的该问题发生在理论和实际彼此背道而驰的情况下。由于当前内存模型的定义,清单 7 中的代码无效。Java 语言规范(Java Language Specification,JLS)要求不能将块中的代码移出来。但是,并没有说不能将块外面的代码移入块中。JIT 编译器会在这里看到一个优化的机会。此优化会删除 /4 和 /5 处的代码,组合并且生成清单 8 中所示的代码。清单 8. 从清单 7 中优化来的代码。 /inst = new Singleton(); /instance = inst;如果进行此项优化,您将同样遇到我们之前讨论过的无序写入问题。用 vol

18、atile 声明每一个变量怎么样?另一个想法是针对变量以及使用关键字volatile。根据 JLS(参见参考资料),声明成volatile的变量被认为是顺序一致的,即,不是重新排序的。但是试图使用来修正双重检查锁定的问题,会产生以下两个问题:这里的问题不是有关顺序一致性的,而是代码被移动了,不是重新排序。即使考虑了顺序一致性,大多数的 JVM 也没有正确地实现第二点值得展开讨论。假设有清单 9 中的代码:清单 9. 使用了 volatile 的顺序一致性class test private volatile boolean stop = false; private volatile int

19、num = 0; public void foo() num = 100; /This can happen second stop = true; /This can happen first /. public void bar() if (stop) num += num; /num can = 0!根据 JLS,由于stopnum被声明为volatile,它们应该顺序一致。这意味着如果曾经是true,num一定曾被设置成100。尽管如此,因为许多 JVM 没有实现的顺序一致性功能,您就不能依赖此行为。因此,如果线程 1 调用foo并且线程 2 并发地调用bar,则线程 1 可能在被设置成为100之前将设置成true。这将导致线程见到是true,而仍被设置成0。使用volatile和 64 位变量的原子数还有另外一些问题,但这已超出了本文的讨论范围。有关此主题的更多信息,请参阅参考资料。解决方案底线就是:无论以何种形式,都不应使用双重检查锁定,因为您不能保证它在任何 JVM 实现上都能顺利运行。JSR-133 是有关内存模型寻址问题的,尽管如此,新的内存模型也不会支持双重检查锁定。因此,您有两种选择:接受如清单 2 中所示的方法的同步。放弃同步,而使用一个static字段。选择项 2 如清单 10 中所示清单 10. 使用 static 字段的单例实现 priv

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2