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()中的代码可能太复杂而无法内联并且无序。
我错了还是格式? :)
答案 0 :(得分:5)
我认为在这种情况下,checkstyle是正确的。在您提供的代码中,考虑如果两个线程在synchronized块的条目处都有row == null
会发生什么。线程A将进入块,并插入新行。然后在线程A退出块后,线程B将进入块(因为它不知道刚刚发生了什么),并尝试再次插入相同的新行。
我看到你刚刚更改了代码,并在那里添加了一个非常重要的缺失行。在 new 代码中,您可能能够避免这种情况,因为两个线程不会依赖于对共享(静态)变量的更改。但是,如果您的DBMS支持INSERT OR UPDATE
等语句,您可能会更好。
将此功能委派给DBMS的另一个好理由是,您需要部署多个应用程序服务器。由于synchronized
块不能跨机器工作,因此无论如何你都必须做其他事情。
答案 1 :(得分:4)
假设您想要读取最里面的行:
row = dao().create(id);
假设dao().fetch
与create方法正确互斥,这不是经典的双重检查锁定问题。
修改 :(代码已更新)
双重检查锁的经典问题是在初始化发生之前分配了一个值,其中两个线程正在访问相同的值。
假设DAO已正确同步且不会返回部分初始化的值,则不会受到双重检查锁定习惯用法的缺陷的影响。
答案 2 :(得分:3)
如果你想写这样的代码,请考虑:
自Java 1.4以来,同步方法变得相当便宜。它不是免费的,但运行时确实没有那么多,以至于值得冒着数据损坏的风险。
从Java 1.5开始,您就拥有了允许您以原子方式读取和设置字段的Atomic *类。不幸的是,它们无法解决您的问题。为什么他们没有添加AtomicCachedReference或者其他东西(当调用get()并且当前值== null时会调用overridable方法)超出我的范围。
尝试ehcache。它允许您设置缓存(即,如果某个键不包含在地图中,则允许您调用代码的对象)。这通常是你想要的,缓存真正解决你的问题(以及你不知道甚至存在的所有其他问题)。
答案 3 :(得分:2)
正如其他人所指出的那样,这段代码将按照您的意图行事,但仅限于一系列严格的非显而易见的假设:
双重检查锁定习惯用法被破坏的原因(根据Java Concurrency in Practice的第16.2.4节)是运行此方法的线程可能看到非空但未正确初始化在进入synchronized块之前引用“row”(除非“dao”提供正确的同步)。如果您的方法使用“row”执行任何操作,而不是检查它是否为null,则它将被破坏。就目前而言,它可能还可以,但非常脆弱 - 如果我认为甚至有一些其他开发人员稍后可能会修改方法的远程机会,我会觉得提交此代码并不舒服不了解DCL的微妙之处。