JDK1.5内存模型下的DCL问题

问题:请看下面的代码,有个两个线程,线程A和线程B,时间上线程A先于线程B执行,线程A刚执行完synchronized代码块(语句7),在构造实例时是乱序执行的,如:第1步,在堆中分配空间;第2步,返回引用给instance变量;第3步,调用LazySingleton的构造函数进行初始化。在JDK1.5的内存模型下,线程A刚执行完synchronized代码块(语句7),线程B开始执行语句2,这时线程B得到的instance,会不会还未完成初始化完。如果不会的话,是volatile保证了构造实例过程的正确顺序,还是synchronized代码块中构造实例的过程压根就不能乱序执行?并请帮忙指出在Java Language Specification的出处。如果会的话,那就说下面的DCL解决方法在JDK1.5的内存模型下依然有问题。

[code="java"]
public class LazySingleton {

private int xxx;

private volatile static LazySingleton instance;

private LazySingleton() {
    this.xxx = 100;                                       // (1)
}

public static LazySingleton getInstance() {
    if (instance == null) {                               // (2)
        synchronized(LazySingleton.class) {               // (3)
            if (instance == null) {                       // (4)
                instance = new LazySingleton();           // (5)
            }                                             // (6)
        }                                                 // (7)
    }                                                     // (8)
    return instance;                                      // (9)
}

public int getXxx() {
    return xxx;                                           // (10)
}

}

[/code]

[quote]OK,我看错了。这个实现有点怪。getInstance是static的,但实现方法用的却是lazy load。锁住.class是正确的,相当于.class是一个共享标志变量而已,保证里面的代码不被并发。但volatile是多余的,static数据是所有线程共享的[/quote]

兄弟,这里的volatile可是关键,可不是多余的,少了volatile,这个DCL就是有问题的,happens-before原则里有“(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。 ”这么一条,关键所在

synchronized锁住的是LazySingleton.class,这是个类对象,并不是对象LazySingleton自身。当然,在锁的时候,对象并未初始化,但this指针是有的。我觉得应该是synchronized(this)才会达到原子化构造函数的目的。
另外,volatile和原子化没有关系

楼主有兴趣帮我看下这个问题
http://www.iteye.com/problems/68538

[quote]synchronized锁住的是LazySingleton.class,这是个类对象,并不是对象LazySingleton自身。当然,在锁的时候,对象并未初始化,但this指针是有的。我觉得应该是synchronized(this)才会达到原子化构造函数的目的。
另外,volatile和原子化没有关系[/quote]

。。。。

楼主的DCL是没有问题的,Monitor都是监控的对象LazySingleton.class也是对象,只是不存放在Heap而已!

貌似不是,你自己写个例子,锁住类对象的时候看其他线程能不能调用构造函数。

[code="java"]class CC implements Runnable
{
public void run()
{
synchronized (A.class) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.print("out");
}
}

class DD implements Runnable
{
public void run()
{
A aa = new A();
}
}[/code]

[quote]
貌似不是,你自己写个例子,锁住类对象的时候看其他线程能不能调用构造函数。

Java代码

class CC implements Runnable

{

public void run()

{

synchronized (A.class) {

try {

Thread.sleep(100000);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

System.out.print("out");

}

}

class DD implements Runnable

{

public void run()

{

A aa = new A();

}

}

[/quote]

Monitor需要一一对应,简单的说,你是加的类锁,还是实例锁!

[quote]
引用

貌似不是,你自己写个例子,锁住类对象的时候看其他线程能不能调用构造函数。

Java代码

class CC implements Runnable

{

public void run()

{

synchronized (A.class) {

try {

Thread.sleep(100000);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

System.out.print("out");

}

}

class DD implements Runnable

{

public void run()

{

A aa = new A();

}

}

Monitor需要一一对应,简单的说,你是加的类锁,还是实例锁!
[/quote]

JMM保证了构造器 Happends-before关系,在构造器初始化对象数据时,不需要在构造器上面加锁!

不知道你说的Monitor和JMM是什么,但单就楼主的问题而言,似乎没有什么联系。在楼主的singleton实现中锁住类对象肯定是错误的,无法达到构造函数不被并发的目的。

参考:
http://en.wikipedia.org/wiki/Double-checked_locking

楼主的这段代码应该是没有问题的,可以使用happens-before原则分析代码的顺序得以验证
happens-before完整规则:

(1)同一个线程中的每个Action(注1)都happens-before于出现在其后的任何一个Action。

(2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。

(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。

(4)Thread.start()的调用会happens-before于启动线程里面的动作。

(5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。

(6)一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。

(7)一个对象构造函数的结束happens-before与该对象的finalizer的开始

(8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。

[url]http://www.javamex.com/tutorials/double_checked_locking_fixing.shtml[/url]

OK,我看错了。这个实现有点怪。getInstance是static的,但实现方法用的却是lazy load。锁住.class是正确的,相当于.class是一个共享标志变量而已,保证里面的代码不被并发。但volatile是多余的,static数据是所有线程共享的