Java通过强制同步两次来双重检查锁定,可行吗?
我已经阅读了有关双重检查锁定修复如何永远不起作用的所有内容,并且我不喜欢延迟初始化,但是能够修复遗留代码会很好,而且这样的问题太诱人了,无法不尝试解决。
这是我的例子: 私有 int timesSafelyGotten = 0; 私人助手helper = null;
public getHelper()
{
if (timesSafelyGotten < 1) {
synchronized (this) {
if (helper == null) {
helper = new Helper();
} else {
timesSafelyGotten++;
}
}
}
return helper;
}
这样,同步代码必须运行一次来创建帮助程序,并且在第一次获取帮助程序时运行一次,因此理论上,直到创建帮助程序的同步代码释放锁并且帮助程序必须完成初始化之后,timesSafelyGotten 才能递增。
我看不出有什么问题,但它是如此简单,似乎好得令人难以置信,你觉得怎么样?
凯莱布·詹姆斯·德莱尔
I've read all about how double checked locking fixes never work and I don't like lazy initialization, but it would be nice to be able to fix legacy code and such a problem is too enticing not to try to solve.
Here is my example:
private int timesSafelyGotten = 0;
private Helper helper = null;
public getHelper()
{
if (timesSafelyGotten < 1) {
synchronized (this) {
if (helper == null) {
helper = new Helper();
} else {
timesSafelyGotten++;
}
}
}
return helper;
}
This way the synchronized code must run once to create the helper and once when it is gotten for the first time so theoretically timesSafelyGotten cannot be incremented until after the synchronized code which created the helper has released the lock and the helper must be finished initializing.
I see no problems, but it is so simple it seems too good to be true, what do you think?
Caleb James DeLisle
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果没有内存屏障(
synchronized
、volatile
或java.util.concurrent
中的等效项),线程可能会看到另一个线程的操作发生在与源代码中出现的顺序不同。由于读取
timesSafelyGotten
时不存在内存障碍,因此对于另一个线程来说,timesSafelyGotten
会在 之前helper
递增> 已分配。这将导致该方法返回null
。实际上,这可能在单元测试期间适用于许多架构。但这是不正确的,最终会在某个地方失败。
双重检查锁定现在确实可以工作,但是正确实现很棘手并且相当昂贵。有一些延迟初始化的模式不太脆弱,更具可读性,并且不需要任何特殊的东西。
Without a memory barrier (
synchronized
,volatile
, or equivalent fromjava.util.concurrent
), a thread may see actions of another thread occur in a different order than they appear in source code.Since there's no memory barrier on the read of
timesSafelyGotten
, it can appear to another thread thattimesSafelyGotten
is incremented beforehelper
is assigned. That would result in returningnull
from the method.In practice, this might work on many architectures during your unit tests. But it's not correct and will eventually fail somewhere.
Double-checked locking does work now, but it's tricky to implement correctly and fairly expensive. There are patterns for lazy initialization that are less fragile, more readable, and don't require anything exotic.
如果您使用的是 JDK5+,请使用 java.util.concurrent,在您的情况下可能是 AtomicInteger。
专门提供这些实用程序是因为没有人能够充分理解低级线程同步原语以使它们正常工作。
If you are using JDK5+, use java.util.concurrent, in your case probably AtomicInteger.
These utilities are provided specifically because no one can be expected to understand the low-level thread synchronization primitives well enough to make them work properly.
那不好。您可以获得 timeSafelyGotten > 1. 示例:
线程 2在同步线上停止,
在同步代码处停止。
在同步代码处停止。
创建助手并离开此块。
增加 timeSafelyGotten 并离开该块。
增加 timeSafelyGotten 并离开该块。
所以 timeSafelyGotten = 2。
您应该再添加一个检查:
或将同步移至上方:
第一种方法更好,因为它不会每次都使用同步。
还有一个提示:不要使用synchronize(this),因为有人也可以使用你的对象进行同步。使用特殊的私有对象进行内部同步:
That's not good. You can get timeSafelyGotten > 1. Example:
stops on synchronization line
stops on synchronization code.
stops on synchronization code.
creates helper and leaves this block.
increment timeSafelyGotten and leaves this block.
increment timeSafelyGotten and leaves this block.
So timeSafelyGotten = 2.
You should add one more check:
or move sync upper:
The first way is better because it doesn't use sync every time.
One more hint: Don't use synchronize(this) because somebody can use your object for synchronization too. Use special private object for internal synchronization: