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

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 the fields.

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 read must happen after the write has taken place。)
关于volatile的详细说明http://blog.csdn.net/fw0124/article/details/6669984

方法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 memory before the object reference itself. In other words, another thread that can "see" the object cannot 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值