原文名称:Double-checked locking and the Singleton pattern
A comprehensive look at this broken programming idiom
1,请首先看一下下面的这个类,只有第一个getInstance()方法是正确的。其中getInstance4()就是由于所谓的"out-of-order"问题引起的(详见注释),而
getInstance5()则是因为编译器优化导致的。
package
org.ding.util;
import
java.util.
*
;
public
class
Singleton
{
private
static
Singleton instance;
private
Vector v;
private
boolean
inUse;
private
Singleton()
{
v
=
new
Vector();
v.addElement(
new
Object());
inUse
=
true
;
}
//
ok
public
static
synchronized
Singleton getInstance()
{
if
(instance
==
null
)
//
1
instance
=
new
Singleton();
//
2
return
instance;
//
3
}
//
error
public
static
Singleton getInstance2()
{
if
(instance
==
null
)
//
1
instance
=
new
Singleton();
//
2 ,此处竞争
return
instance;
//
3
}
//
error
public
static
Singleton getInstance3()
{
if
(instance
==
null
)
{
synchronized
(Singleton.
class
)
{
//
此处竞争
instance
=
new
Singleton();
}
}
return
instance;
}
/**/
/*
*error
*known as double-checke
*
*The issue of the failure of double-checked locking is not due to implementation bugs in JVMs
*but to the current Java platform memory model.
*The memory model allows what is known as "out-of-order writes" and is a prime reason why this idiom fails.
*
*看一下代码的//3处是如何执行的:
* mem = allocate(); //a,Allocate memory for Singleton object.
* instance = mem; //b,Note that instance is now non-null, but has not been initialized.
* ctorSingleton(instance); //c,Invoke constructor for Singleton passing instance.
*/
public
static
Singleton getInstance4()
{
if
(instance
==
null
)
{
//
1
synchronized
(Singleton.
class
)
{
//
2
if
(instance
==
null
)
//
3
instance
=
new
Singleton();
//
3,此处线1程如果执行完b,但还没有执行c的时候,线程2执行
//
1,会返回未完全初始化的对象
}
}
return
instance;
}
//
error
//
这个的问题在于:
//
The Java Language Specification (JLS) demands that code within a synchronized block not be moved out of a synchronized block.
//
However, it does not say that code not in a synchronized block cannot be moved into a synchronized block.
/** */
/**
* getInstance5 error
*
* 这个方法失败的问题在于:
* The Java Language Specification (JLS) demands that code within a synchronized block not be moved out of a synchronized block.
* However, it does not say that code not in a synchronized block cannot be moved into a synchronized block.
*
* 所以//3至//5之间的代码会被编译器优化为:
* if (inst == null) {
* synchronized (Singleton.class) { //3
* instance = new Singleton();
* }
* }
*
*
@return
Singleton
*/
public
static
Singleton getInstance5()
{
if
(instance
==
null
)
{
synchronized
(Singleton.
class
)
{
//
1
Singleton inst
=
instance;
//
2
if
(inst
==
null
)
{
synchronized
(Singleton.
class
)
{
//
3
inst
=
new
Singleton();
//
4
}
instance
=
inst;
//
5
}
}
}
return
instance;
}
}
2,还有一种正确的方式
class Singleton2 {
private Vector v;
private boolean inUse;
private static Singleton2 instance = new Singleton2();
private Singleton2() {
v = new Vector();
inUse = true;
//
}
public static Singleton2 getInstance() {
return instance;
}
} 3,
volitile为什么不行?
Another idea is to use the keyword volatile
for the variables inst
and instance
. According to the JLS (see Resources), variables declared volatile
are supposed to be sequentially consistent, and therefore, not reordered. But two problems occur with trying to use volatile
to fix the problem with double-checked locking:
- The problem here is not with sequential consistency. Code is being moved, not reordered.
- Many JVMs do not implement
volatile
correctly regarding sequential consistency anyway.
The second point is worth expanding upon. Consider the code in Listing 9:
Listing 9. Sequential consistency with volatileclass test
{
private volatile boolean stop = false;
private volatile int 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!
}
//...
}
|
According to the JLS, because stop
and num
are declared volatile
, they should be sequentially consistent. This means that if stop
is ever true
, num
must have been set to 100
. However, because many JVMs do not implement the sequential consistency feature of volatile
, you cannot count on this behavior. Therefore, if thread 1 called foo
and thread 2 called bar
concurrently, thread 1 might set stop
to true
before num
is set to 100
. This could lead thread 2 to see stop
as true
, but num
still set to 0
. There are additional problems with volatile
and the atomicity of 64-bit variables, but this is beyond the scope of this article. See Resources for more information on this topic.
4,String类会有"out-of-order"问题么?
答案是比较老的版本会有:
"Running this code on old JVMs like Sun JDK 1.2.1 results in the out-of-order write problem, and thus, a non-immutable String
."
"Both the IBM 1.3 and Sun 1.3 JVMs produce immutable String
s as expected."
class StringReader extends Thread {
MutableString ms;
public StringReader(MutableString muts) {
ms = muts;
}
public void run() {
while (true) {
if (!(ms.str.equals("hello"))) { //2
System.out.println("String is not immutable!");//Peter Haggar 说此处是由输出的。
break;
}
}
}
}
class MutableString {
public String str; //3
public static void main(String args[]) {
MutableString ms = new MutableString(); //4
new StringCreator(ms).start(); //5
new StringReader(ms).start(); //6
}
}