【问题标题】:Using Java ReadWriteLock to synchronise cached data - whether to mark state variables volatile?使用 Java ReadWriteLock 同步缓存数据——是否将状态变量标记为 volatile?
【发布时间】:2015-02-03 11:07:53
【问题描述】:

ReadWriteLock javadoc at Oracleits implementation 描述了锁的作用以及如何使用它,但没有说明是否使用volatile 关键字。

这与do-all-mutable-variables-need-to-be-volatile-when-using-locks 不是同一个问题,因为我很高兴锁将正确同步访问和可见性,但是将volatile 用于变量仍然是一个好主意,例如出于编译器优化或任何其他原因?

我的缓存数据由一个很少更改的List 和几个Maps 组成,这些Maps 使用对象的各种属性映射列表中的对象。

private void reload() {
    Set<Registration> newBeans = dao.listRegistered();
    beans = Collections.unmodifiableSet(newBeans);
    codeToBeanMap.clear();
    userToBeanMap.clear();
    nameToBeanMap.clear();
    idToBeanMap.clear();
    for (Registration bean : newBeans) {
        codeToBeanMap.put(bean.getCode(), bean);
        userToBeanMap.put(bean.getUser(), bean);
        nameToBeanMap.put(bean.getName(), bean);
        idToBeanMap.put(bean.getId(), bean);
    }
}

最好的声明是什么?我有这个:

private Set<Registration> ecns;
private final Map<String, Registration> codeToBeanMap =
        new HashMap<String, Registration>();
private final Map<String, Registration> userToBeanMap =
        new HashMap<String, Registration>();
private final Map<String, Registration> nameToBeanMap =
        new HashMap<String, Registration>();
private final Map<String, Registration> idToBeanMap =
        new HashMap<String, Registration>();

【问题讨论】:

    标签: java multithreading concurrency reentrantreadwritelock readwritelock


    【解决方案1】:

    如果您要专门从由锁保护的代码访问变量,那么将该变量指定为 volatile 将是多余的,并且会导致一定的性能损失(它在纳秒范围内)。

    【讨论】:

      【解决方案2】:

      你的这样的声明对于并发来说很好:

      private final Map<String, Registration> idToBeanMap = new HashMap<>();
      

      ... 因为 Java 保证声明为 final 的字段将在其他线程可以访问它们之前被初始化(至少 Java 1.5 起)。 volatile 关键字不是必需的(无论如何对 final 没有意义)。

      但是,这并没有说明 HashMap 的内容 - 这不是线程安全的,不同的线程可能会看到不同或不一致的内容,除非您使用同步代码块。您最简单的解决方案是改用 ConcurrentHashMap 。这将保证所有线程都能看到预期的地图内容。

      但是,这只意味着地图上的操作将是原子的。在您上面的代码中,您可能已经清除了一个地图,但没有清除其他地图(例如),此时另一个线程尝试读取可能会产生不一致结果的数据。

      所以,因为看起来您的代码需要多个 Map 来保持同步和相互一致,唯一的方法是使您的 reload 方法同步,并让客户端通过同一对象上的同步方法读取数据.

      【讨论】:

      • 好。这与ReentrantReadWriteLock javadoc 一致,其中包含一个示例,这正是我使用它的方式。主要区别在于我没有声明主要的Set final,因为我想在每次重新加载时从Collections.unmodifiableSet(setFromDao) 分配它。这看起来可能是一个大问题 - 如何声明一个 set final 并更新它。
      【解决方案3】:

      但是对变量使用 volatile 仍然是一个好主意,例如出于编译器优化或任何其他原因?

      如果有任何 volatile 阻止而不是启用编译器优化。

      Oracle 的 ReadWriteLock javadoc 及其实现描述了锁的作用以及如何使用它,但没有说明是否使用 volatile 关键字。

      确实如此,事实上,如果您查看读写锁实现的接口,特别是 Lock interface 声明,实现必须提供与 synchronized() 块相同的内存可见性保证:

      所有 Lock 实现必须强制执行与内置监视器锁提供的相同内存同步语义,如 Java™ 语言规范第 17.4 节所述:

      • 成功的锁定操作与成功的锁定操作具有相同的内存同步效果。
      • 成功的解锁操作与成功的解锁操作具有相同的内存同步效果。

      【讨论】:

        猜你喜欢
        • 2011-08-30
        • 1970-01-01
        • 2019-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-13
        相关资源
        最近更新 更多