使用没有readlock的writeLock是否安全?

时间:2009-04-22 06:28:31

标签: java concurrency

假设这是一个“单例”实现:我保证这只会调用productCatalogLoader.load()一次,而且也不会发生nullpointers吗?有什么方法可以使这更简单?

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ProductCatalog productCatalog;

public ProductCatalog get() {
        if (this.productCatalog == null) {
            reload();
         }
        return this.productCatalog;
}                                                         

public void reload() {

    lock.writeLock().lock();
    try {
        if (this.productCatalog != null) return;
        this.productCatalog = productCatalogLoader.load();
    } finally {
        lock.writeLock().unlock();
    }
}

}

编辑: 这是一个适度成功的尝试,将更复杂的代码简化为简单的问题样本。有几个人发现我的单身太复杂了;)(实际上还有一个石英计时器调用重载,但“重载”实现有点不同IRL)。无论如何,我得到了我需要的答案,双重检查锁定。

4 个答案:

答案 0 :(得分:4)

使IMN变得更简单的最好方法是使用“正常”锁定,除非你确实有充分的证据证明它导致了瓶颈:

private final Object lock = new Object();
private ProductCatalog productCatalog;

public ProductCatalog get() {
    synchronized (lock) {
        if (this.productCatalog == null) {
            this.productCatalog = productCatalogLoader.load();
        }
        return this.productCatalog;
    }
}                                                         

在绝大多数情况下,这将足够好,您无需担心内存模型的技术问题。

编辑:关于在没有获取读锁定的情况下读取写入锁定中更改的数据是否安全 - 我怀疑没有,但我不想肯定地说。使用普通(和Java 1.5+上的安全,如果你小心)使用volatile变量进行双重检查锁定是否有任何好处?

private final Object lock = new Object();
private volatile ProductCatalog productCatalog;

public ProductCatalog get() {
    if (this.productCatalog == null) {
        synchronized (lock) {
            if (this.productCatalog == null) {
                this.productCatalog = productCatalogLoader.load();
            }
        }
    }
    return this.productCatalog;
}                                                         

我认为这是有效Java第2版中推荐的延迟初始化技术,适用于静态初始化程序不够的情况。请注意,productCatalog必须是易变的才能发挥作用 - 而且我认为有效地通过不取消代码中的读取锁定而错过了您所缺少的内容。

答案 1 :(得分:3)

不,一般情况下它并不安全......只有在其他任何人能够获得一个读锁定或写入锁定之后才能保证读取锁定或写入锁定。 不在代码中使用readlock就像重新加载方法同步但不是get方法。我在这里同意Jon Skeet,但这在很大程度上取决于使用情况。

如果你有很多线程可以或多或少同时调用get,你可能需要一个readlock而不是同步。

编辑:顺便说一下,在Effective Java 2nd Edition中如何做到这一点有一个很好的例子...... 我也必须在这里纠正自己,鉴于上下文,如果以一种聪明的方式完成同步也会同样有效(并且在这种情况下,writelock可能也会起作用)。

答案 2 :(得分:2)

这看起来像broken double checked locking。问题是这个.productCatalog可以在对象完全构建之前分配。

说我们有

var foo = new ProductCatalog();
foo.blah = "blah blah";
this.productCatalog = foo;

可以重新排序为

var foo = new ProductCatalog();
this.productCatalog = foo;
foo.blah = "blah blah";

答案 3 :(得分:0)

如果你想做一个单身人士,这不是更简单:

private final ProductCatalog productCatalog = productCatalogLoader.load();
public ProductCatalog get() {
   return this.productCatalog;
}

如果有一些原因导致您无法将ProductCatalog作为最终成员,那么只要使用每个Jon Skeet的答案或原始代码,只要不通过该get()调用访问成员productCatalog,或者你的重装()。