5

Checkstyle 将此代码报告为“双重检查锁定习惯用法已损坏”,但我认为我的代码实际上并未受到双重检查锁定问题的影响。

如果不存在具有该 id 的行,则该代码应该在数据库中创建一行。它在多线程环境中运行,我想避免主键存在 SQL 异常。

伪代码:

private void createRow(int id) {
  Row row = dao().fetch(id);
  if (row == null) {
     synchronized (TestClass.class) {
        row = dao().fetch(id);
        if (row == null) {
           dao().create(id);
        }
     }
  }
}

我同意它看起来像双重检查锁定,但我没有使用静态变量,并且 fetch() 和 create() 中的代码可能太复杂而无法内联和乱序。

我错了还是检查式?:)

4

4 回答 4

5

我认为在这种情况下, checkstyle 是正确的。在您提供的代码中,考虑如果两个线程都row == null在同步块的入口处会发生什么。线程 A 将进入块,并插入新行。然后在线程 A 退出该块后,线程 B 将进入该块(因为它不知道刚刚发生了什么),并尝试再次插入相同的新行。

我看到您刚刚更改了代码并在其中添加了一个非常重要的缺失行。在代码中,您也许可以避免这种情况,因为两个线程不会依赖对共享(静态)变量的更改。但是您最好看看您的 DBMS 是否支持诸如INSERT OR UPDATE.

将此功能委托给 DBMS 的另一个很好的理由是,如果您需要部署多个应用程序服务器。由于synchronized块不能跨机器工作,因此无论如何您都必须做其他事情。

于 2008-12-01T06:56:15.393 回答
4

假设您希望最里面的行读取:

row = dao().create(id);

dao().fetch假设与 create 方法正确互斥,这不是经典的双重检查锁问题。

编辑:(代码已更新)

双重检查锁的经典问题是在两个线程访问相同值的初始化发生之前分配一个值。

假设 DAO 正确同步并且不会返回部分初始化的值,这不会受到双重检查锁习惯用法的缺陷的影响。

于 2008-12-01T06:54:54.257 回答
3

如果您想编写这样的代码,请考虑:

  • 从 Java 1.4 开始,同步方法变得非常便宜。它不是免费的,但运行时确实不会受到太大的影响,值得冒数据损坏的风险。

  • 从 Java 1.5 开始,您拥有 Atomic* 类,允许您以原子方式读取和设置字段。不幸的是,它们不能解决您的问题。为什么他们没有添加 AtomicCachedReference 或其他东西(当调用 get() 并且当前值 == null 时会调用可覆盖的方法)超出了我的理解。

  • 试试ehcache。它允许您设置缓存(即,如果映射中包含键,则允许您调用代码的对象)。这通常是您想要的,并且缓存真正解决了您的问题(以及您甚至不知道它们存在的所有其他问题)。

于 2008-12-01T08:52:18.823 回答
2

正如其他人指出的那样,此代码将按原样执行您的预期,但仅在一组严格的非显而易见的假设下:

  1. Java 代码是非集群的(参见@Greg H 的回答)
  2. 在同步块之前的第一行中检查“行”引用是否为空。

双重检查锁定习惯用法被破坏的原因(根据Java Concurrency in Practice的第16.2.4节)是运行此方法的线程在进入同步块(除非“dao”提供正确的同步)。如果您的方法除了检查它是否为空之外,还对“行”做任何事情,它就会被破坏。就目前而言,它可能还可以,但非常脆弱- 如果我认为其他开发人员稍后可能会在不了解 DCL 的微妙之处的情况下修改该方法的可能性很小,那么我个人不会愿意提交此代码。

于 2008-12-02T18:53:39.080 回答