这是双重检查锁定损坏了吗?
-
11-07-2019 - |
题
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 时,它会调用可重写的方法)超出了我的范围。
尝试 高速缓存. 。它允许您设置缓存(即和对象,如果有键,则允许您调用代码 不是 包含在地图中)。这通常是您想要的,并且缓存确实解决了您的问题(以及您甚至不知道它们存在的所有其他问题)。
正如其他人指出的那样,这段代码将按原样执行您的意图,但仅在一组严格的非显而易见的假设下进行:
- Java代码是非集群的(参见@Greg H的回答)
- “行”参考是 仅有的 在同步块之前的第一行中检查 null 。
双重检查锁定惯用语被破坏的原因(根据第 16.2.4 节) Java 并发实践)是运行此方法的线程有可能看到非空值,但是 初始化不当 在进入同步块之前引用“row”(除非“dao”提供适当的同步)。如果您的方法除了检查“行”是否为空之外还对“行”执行任何操作,那么它就会被破坏。就目前情况而言,可能没问题,但是 非常脆弱 - 就个人而言,如果我认为其他开发人员在稍后的某个时间可能会在不了解 DCL 微妙之处的情况下修改该方法,那么我将不愿意提交此代码。