问题:请看下面的代码,有个两个线程,线程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数据是所有线程共享的