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() 中的代码可能太复杂,无法内联和乱序。

是我错了还是checkstyle错了?:)

有帮助吗?

解决方案

假设你想要的最里行改为:

row = dao().create(id);

这不是一个经典双重检查锁定问题假设dao().fetch被正确地从创建方法mutexed。

修改:(代码已更新)

一个双重检查锁的经典问题发生之前初始化,其中两个线程都访问同一值是具有值赋值。

假设DAO正确同步并且不会返回一个部分初始化值,这不会从双重检查锁定成语的缺陷困扰。

其他提示

我认为在这种情况下,CheckStyle的是正确的。在你的代码提交,考虑一下,如果两个线程都在进入synchronized块已经row == null会发生什么。线程A将进入该块,并插入新行。再经过线程A退出块,线程B将进入块(因为它不知道刚刚发生了什么),并尝试再次插入相同的新行。

我看你只是改变了代码,并在那里加入了非常重要的缺失行。在的代码,你也许能得逞的,因为两个线程不会依靠变为共享(静态)变量。但是,你可能会更好看,如果你的DBMS支持的语句,如INSERT OR UPDATE

另一个很好的理由委派此功能的DBMS是如果你需要部署多个应用程序服务器。由于synchronized块不跨机器工作,你将不得不做别的事情在这种情况下,无论如何。

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

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

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

  • 尝试 高速缓存. 。它允许您设置缓存(即和对象,如果有键,则允许您调用代码 不是 包含在地图中)。这通常是您想要的,并且缓存确实解决了您的问题(以及您甚至不知道它们存在的所有其他问题)。

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

  1. Java代码是非集群的(参见@Greg H的回答)
  2. “行”参考是 仅有的 在同步块之前的第一行中检查 null 。

双重检查锁定惯用语被破坏的原因(根据第 16.2.4 节) Java 并发实践)是运行此方法的线程有可能看到非空值,但是 初始化不当 在进入同步块之前引用“row”(除非“dao”提供适当的同步)。如果您的方法除了检查“行”是否为空之外还对“行”执行任何操作,那么它就会被破坏。就目前情况而言,可能没问题,但是 非常脆弱 - 就个人而言,如果我认为其他开发人员在稍后的某个时间可能会在不了解 DCL 微妙之处的情况下修改该方法,那么我将不愿意提交此代码。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top