Java 使用不带readlock的writeLock安全吗?

Java 使用不带readlock的writeLock安全吗?,java,concurrency,Java,Concurrency,假设这是一个单例实现:我是否保证只调用productCatalogLoader.load一次,并且不会出现空指针?有没有办法让这更简单 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private ProductCatalog productCatalog; public ProductCatalog get() { if (this.productCatalog == nul

假设这是一个单例实现:我是否保证只调用productCatalogLoader.load一次,并且不会出现空指针?有没有办法让这更简单

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();
    }
}
}

编辑:
这是一次相当成功的尝试,将更复杂的代码简化为一个简单的问题示例。有几个人意识到我的单身生活太复杂了;实际上还有一个石英计时器调用reload,但是reload实现与IRL有点不同。我得到了我需要的答案,打破了双重检查锁定。

在我看来,让它更简单的最好方法是使用普通锁,除非你确实有很好的证据证明它造成了瓶颈:

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;
    }
}                                                         
在绝大多数情况下,这已经足够好了,您不需要担心内存模型的技术细节

编辑:至于在不获取读锁的情况下读取写锁中更改的数据是否安全,我怀疑不安全,但我不想肯定地说。如果您小心地使用volatile变量进行双重检查锁定,那么在Java1.5+上使用普通和安全的方法有什么好处吗

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;
}                                                         

我相信这是有效的Java2ndEdition中推荐的惰性初始化技术,适用于静态初始值设定项不足的情况。请注意,productCatalog必须是易失性的,才能正常工作。我认为,如果不去掉代码中的读锁,这就是您实际上缺少的。IMO,使其更简单的最佳方法是使用普通锁,除非您确实有充分的证据证明它会造成瓶颈:

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;
    }
}                                                         
在绝大多数情况下,这已经足够好了,您不需要担心内存模型的技术细节

编辑:至于在不获取读锁的情况下读取写锁中更改的数据是否安全,我怀疑不安全,但我不想肯定地说。如果您小心地使用volatile变量进行双重检查锁定,那么在Java1.5+上使用普通和安全的方法有什么好处吗

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;
}                                                         

我相信这是有效的Java2ndEdition中推荐的惰性初始化技术,适用于静态初始值设定项不足的情况。请注意,productCatalog必须是易失性的,才能正常工作。我认为,如果不取出代码中的读锁,这就是您缺少的内容。

不,一般来说,这是不安全的。。。writelock仅保证在释放writelock之前,其他任何人都无法获得readlock或writelock。 不在代码中使用readlock类似于同步了reload方法而不是get方法。我有点同意Jon Skeet的观点,但这在很大程度上取决于用法

如果您有很多线程调用get或多或少是同时进行的,那么您可能想要一个readlock,而不是同步它

编辑:顺便说一句,在有效的Java第二版中有一个很好的例子。。。
我还必须在这里纠正我自己,考虑到上下文,如果以一种智能的方式进行同步,那么同步也会工作得很好,而writelock可能也会在这种上下文中工作。

不,一般来说,它不安全。。。writelock仅保证在释放writelock之前,其他任何人都无法获得readlock或writelock。 不在代码中使用readlock类似于同步了reload方法而不是get方法。我有点同意Jon Skeet的观点,但这在很大程度上取决于用法

如果您有很多线程调用get或多或少是同时进行的,那么您可能想要一个readlock,而不是同步它

编辑:顺便说一句,在有效的Java第二版中有一个很好的例子。。。
我还必须在这里纠正自己的错误,考虑到上下文,如果以一种智能的方式进行同步,那么同步也会很好地工作,而writelock可能也会在这种上下文中工作。

如果您试图让一个单例执行以下操作,它不是更简单吗:

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

如果由于某种原因无法使ProductCatalog成为最终成员,那么使用synchronized per Jon Skeet的答案或原始代码应该可以工作,只要成员ProductCatalog未被访问,除非通过get调用或重新加载

如果你想让单身汉做以下事情,不是更简单吗

private final ProductCatalog productCatalog = productCatalogLoader.load();
public ProductCatalog get() {
   return this.productCatalog;
}
如果由于某种原因无法使ProductCatalog成为最终成员,那么使用synchronized per Jon Skeet的答案或原始代码应该可以工作,只要成员ProductCatalog未被访问,除非通过get调用或重新加载

这看起来像。问题在于,可以在完全构造对象之前指定productCatalog

说我们有

var foo = new ProductCatalog();
foo.blah = "blah blah";
this.productCatalog = foo;
可以按以下方式重新排序

var foo = new ProductCatalog();
this.productCatalog = foo;
foo.blah = "blah blah";
这看起来像。问题在于,可以在完全构造对象之前指定productCatalog

说我们有

var foo = new ProductCatalog();
foo.blah = "blah blah";
this.productCatalog = foo;
可以按以下方式重新排序

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

嗯,我倾向于同意。但是使用不带r的写锁安全吗
艾德洛克?可以说更复杂,是的,我倾向于同意。但是,在没有读锁的情况下使用写锁安全吗?可以说,更复杂的是,关键短语是不安全的发布。关键短语是不安全的发布。