5

下面的代码片段来自 Effective Java 2nd Edition Double Checked Locking

// 仔细检查实例字段延迟初始化的习惯用法

private volatile FieldType field;

FieldType getField() {
    FieldType result = field;
    if (result == null) {  // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null)// Second check (with locking)  
                field = result = computeFieldValue();
        }
    }
    return result;
}

据我所知,双重检查锁定的主要问题是第二次检查锁定内部的重新排序,以便其他线程可能会看到已设置的字段/结果的值实际上可能仍在执行中。为了避免这种情况,我们将字段的引用设置为 volatile 以保证可见性和重新排序。

但这也可以通过下面的代码来实现

private FieldType field; // non volatile
private volatile boolean fence = false;

FieldType getField() {
    if (field == null) {  // First check (no locking) // no volatile read
        synchronized(this) {   //  inside synch block no problem of visibilty will latest           //value  of field 
            if (field == null) {// Second check (with locking)  
                Object obj =  computeFieldValue();
             fence = true; // any volatile write will take. this will make sure statements are //not reorder with setting field as non null.
            field = (FieldType)obj; // this will be only set after computeFieldValue has been //completed fully
           }
        }
    }
    return field;
}

因此,在完成初始化后,没有线程需要进行易失性读取或同步开销。请看看我的假设是否正确?

4

3 回答 3

9

JLS(第17.4.5 节)规定:

“对 volatile 字段(第 8.3.1.4 节)的写入发生在对该字段的每次后续读取之前。”

您不会在更新后读取fence变量,因此更新的线程fence与任何第二个线程之间没有“发生之前”的关系。这意味着第二个线程不能保证看到field第一个线程对变量所做的更新。

简而言之,您的“改进”代码是双重检查锁定的错误实现。

于 2013-06-18T13:02:56.913 回答
3

在纯 JMM 中没有办法实现“便宜”的双重检查锁定;必须给予一些东西。

您的解决方案不起作用,因为可以通过以下正常操作重新排序易失性写入。有关在所谓的“roach motel”模型中允许重新排序的信息,请参阅jsr133 食谱。“Roach motel”是比 JMM 更强大的模型,因此如果您的解决方案在 roach motel 中失败,那么它在 JMM 中也会失败。

roach motel model
reordering between a normal action and a volatile/monitor action

   --                              <--
  |                                   |
  |    VolatileLoad / MonitorEnter    | forbidden
  |                                   |
   --> allowed                      --


   --> allowed                      --
  |                                   | 
  |    VolatileStore / MonitorExit    | forbidden
  |                                   |
   --                              <--

有一种方法可以阻止“蟑螂汽车旅馆”模型中两个正常动作的重新排序

(1) action#1
(2) volatile write
(3) volatile read
(4) action#4

(1) 不能与 (2) 重新排序,(4) 不能与 (3) 重新排序,因此 (1) 和 (4) 不能重新排序。

但是,请注意,“蟑螂汽车旅馆”模型比 JMM 模型更强大。您不能确定 JVM 是否符合 roach motel 模型。举个具体的例子

action#1
synchronized(new Object()){}
synchronized(new Object()){}
action#4

根据 roach motel,action#1 和 action#4 不能重新排序;但是 JVM 可以合法地(由 JMM 允许)删除两个同步块,然后重新排序剩余的两个操作。

于 2013-06-19T02:03:34.847 回答
0

根据我信任的消息来源,它似乎是一个有效且安全的代码。volatile 变量的写入必须强制所有其他变量的写入,并且既不能使用 volatile 也不能使用普通赋值重新排序。

于 2016-07-27T12:44:29.617 回答