您的位置:首页 > 编程语言 > Java开发

Java中的Double-checked Locking (DCL)问题

2014-01-21 15:48 288 查看
Double-checked Locking (DCL)用来在lazy initialisation 的单例模式中避免同步开销的一个方法。

下面是这么做的一个例子。

public class MyFactory {
private static MyFactory instance;

public synchronized static MyFactory getFactory() {
if (instance == null)
instance = new MyFactory();
return instance;
}
}


上面的例子是完全正确的。但是考虑到所有的Read操作也需要同步,为了避免昂贵的同步开销,似乎有如下做法:

public class MyBrokenFactory {
private static MyFactory instance;
private int field1, field2 ...

public static MyBrokenFactory getFactory() {
// This is incorrect: don't do it at home, kids!
if (instance == null) {
synchronized (MyBrokenFactory.class) {
if (instance == null)
instance = new MyFactory();
}
}
return instance;
}

private MyBrokenFactory() {
field1 = ...
field2 = ...
}
}


但是上面的做法是不正确的,考虑2个线程同时调用MyBrokenFactory.getFactory(),线程2在线程1完成对象的初始化之前就可能得到了对象的引用。

Thread 1: 'gets in first' and starts creating instance.Thread 2: gets in just as Thread 1 has written the object reference to memory, but before it has written all thefields.
1. Is instance null? Yes.

2. Synchronize on class.

3. Memory is allocated for instance.

4. Pointer to memory saved into instance.

7. Values for field1 and field2 are written to memory allocated for object.
5. Is instance null? No.

6. instance is non-null, but field1 and field2 haven't yet been set! This thread sees invalid values for field1 and field2!
如果解决上面的问题呢?

方法1:使用class loader

public class MyFactory {
private static final MyFactory instance = new MyFactory();

public static MyFactory getInstance() {
return instance;
}

private MyFactory() {}
}


如果需要处理异常情况,

public class MyFactory {
private static final MyFactory instance;

static {
try {
instance = new MyFactory();
} catch (IOException e) {
throw new RuntimeException("Darn, an error's occurred!", e);
}
}

public static MyFactory getInstance() {
return instance;
}

private MyFactory() throws IOException {
// read configuration files...
}
}


但是这样就失去了lazy initialisation带来的好处,Java5以后还有一种办法,

方法2:使用DCL+volatile

JAVA5以后如果申明实例引用为volatile,那么DCL就是OK的。

public class MyFactory {
private static volatile MyFactory instance;

public static MyFactory getInstance(Connection conn)
throws IOException {
if (instance == null) {
synchronized (MyFactory.class) {
if (instance == null)
instance = new MyFactory(conn);
}
}
return instance;
}

private MyFactory(Connection conn) throws IOException {
// init factory using the database connection passed in
}
}


JAVA5以后,访问一个volatile的变量具有synchronized 的语义。换句话说,JAVA5保证unsycnrhonized volatile read 会在写之后发生。(Accessing a volatile variable has the semantics of synchronization as of Java 5. In other words Java
5 ensures that the unsycnrhonized volatile readmust happen after the write has taken place。)

关于volatile的详细说明/article/8045250.html

方法3:Factory类的所有字段都是final字段

JAVA5之后,如果在constructor中对final字段赋值,JVM保证先把这些值提交到内存,然后才会更新内存中的对象引用。

换句话说,另外一个能看到这个对象的线程不能看到没有初始化过的final字段。

在Factory类的所有字段都是final字段这种情况下,我们实际上没有必要申明factory实例为volatile。

In Java 5, a change was made to the definition of final fields. Where the values of these fields are set in the constructor, the JVM ensures that these values are committed to main memorybefore
the object reference itself. In other words, another thread that can "see" the objectcannot ever see uninitialised values of its final fields. In that case, we wouldn't actually need to declare the instance reference as volatile.

(原文http://javamex.com/tutorials/double_checked_locking_fixing.shtml)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: