Java通过强制同步两次来双重检查锁定,可行吗?

发布于 2024-08-14 08:01:28 字数 627 浏览 7 评论 0原文

我已经阅读了有关双重检查锁定修复如何永远不起作用的所有内容,并且我不喜欢延迟初始化,但是能够修复遗留代码会很好,而且这样的问题太诱人了,无法不尝试解决。

这是我的例子: 私有 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

不可一世的女人 2024-08-21 08:01:28

如果没有内存屏障(synchronizedvolatilejava.util.concurrent 中的等效项),线程可能会看到另一个线程的操作发生在与源代码中出现的顺序不同。

由于读取 timesSafelyGotten 时不存在内存障碍,因此对于另一个线程来说,timesSafelyGotten 会在 之前 helper 递增> 已分配。这将导致该方法返回 null

实际上,这可能在单元测试期间适用于许多架构。但这是不正确的,最终会在某个地方失败。

双重检查锁定现在确实可以工作,但是正确实现很棘手并且相当昂贵。有一些延迟初始化的模式不太脆弱,更具可读性,并且不需要任何特殊的东西。

Without a memory barrier (synchronized, volatile, or equivalent from java.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 that timesSafelyGotten is incremented before helper is assigned. That would result in returning null 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.

笨笨の傻瓜 2024-08-21 08:01:28

如果您使用的是 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.

执妄 2024-08-21 08:01:28

那不好。您可以获得 timeSafelyGotten > 1. 示例:

  1. Thread1 检查是否成功并且
    线程 2在同步线上停止,
  2. 检查是否成功
    在同步代码处停止。
  3. Thread3 检查是否成功并且
    在同步代码处停止。
  4. Thread1 落入同步块,
    创建助手并离开此块。
  5. Thread2 落入同步块,
    增加 timeSafelyGotten 并离开该块。
  6. Thread3 落入同步块,
    增加 timeSafelyGotten 并离开该块。

所以 timeSafelyGotten = 2。

您应该再添加一个检查:

if (helper == null) {
    helper = new Helper();
} else if (timesSafelyGotten < 1) {
    timesSafelyGotten++;
}

或将同步移至上方:

synchronized(this) {
   if (timeSafelyGotten < 1) {
       ...
   }
}

第一种方法更好,因为它不会每次都使用同步。

还有一个提示:不要使用synchronize(this),因为有人也可以使用你的对象进行同步。使用特殊的私有对象进行内部同步:

classs MyClass {
    private Object syncRoot = new Object();

    ...
    synchronized(syncRoot) {
        ....
    }
}

That's not good. You can get timeSafelyGotten > 1. Example:

  1. Thread1 checks if successfully and
    stops on synchronization line
  2. Thread2 checks if successfully and
    stops on synchronization code.
  3. Thread3 checks if successfully and
    stops on synchronization code.
  4. Thread1 falls into sync block,
    creates helper and leaves this block.
  5. Thread2 falls into sync block,
    increment timeSafelyGotten and leaves this block.
  6. Thread3 falls into sync block,
    increment timeSafelyGotten and leaves this block.

So timeSafelyGotten = 2.

You should add one more check:

if (helper == null) {
    helper = new Helper();
} else if (timesSafelyGotten < 1) {
    timesSafelyGotten++;
}

or move sync upper:

synchronized(this) {
   if (timeSafelyGotten < 1) {
       ...
   }
}

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:

classs MyClass {
    private Object syncRoot = new Object();

    ...
    synchronized(syncRoot) {
        ....
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文