开发工具:
文件大小: 209kb
下载次数: 0
上传时间: 2019-03-23
详细说明: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最新版进行解压.
- 如果您发现内容无法下载,请稍后再次尝试;或者到消费记录里找到下载记录反馈给我们.
- 下载后发现下载的内容跟说明不相乎,请到消费记录里找到下载记录反馈给我们,经确认后退回积分.
- 如下载前有疑问,可以通过点击"提供者"的名字,查看对方的联系方式,联系对方咨询.