局部变量上的同步

2022-09-01 09:00:04

我有一个多线程Java代码,其中:

  • 多个线程从同步共享存储中读取有状态对象(即,因此,某些线程可能引用相同的对象);
  • 然后,每个线程调用一个方法并将其对象传递到那里;process()
  • process()以某种方式处理对象,这可能会导致对象状态的更改;
  • 这些状态更改应同步。

我创建了一个这样的方法:

public void process(Foo[] foos) {
    for (final Foo foo : foos) {
        if (foo.needsProcessing()) {
            synchronized (foo) {
                foo.process();  // foo's state may be changed here
            }
        }
    }
}

据我所知,这看起来是合法的。但是,IntelliJ的检查抱怨在局部变量上进行同步,因为“不同的线程很可能具有不同的本地实例”(这对我来说无效,因为我没有在方法中初始化foos)。

从本质上讲,我在这里想要实现的与同步方法Foo.process()相同(这对我来说不是一个选择,因为Foo是第三方库的一部分)。

我已经习惯了没有黄色标记的代码,所以来自社区的任何建议都值得赞赏。在本地进行同步真的那么糟糕吗?有没有替代方案可以在我的情况下工作?

提前致谢!


答案 1
if (foo.needsProcessing()) {
    synchronized (foo) {
        foo.process();  // foo's state may be changed here
    }
}

我认为上述片段中存在一个争用条件,可能导致偶尔在同一对象上被调用两次。它应该是:foo.process()

synchronized (foo) {
    if (foo.needsProcessing()) {
        foo.process();  // foo's state may be changed here
    }
}

这在本地同步真的那么糟糕吗?

在局部变量本身上进行同步还不错。真正的问题是:

  • 不同的线程是否在正确的对象上进行同步以实现适当的同步,以及

  • 其他内容是否会通过同步这些对象而导致问题。


答案 2

Stephen C的答案有问题,他毫无意义地输入了很多同步锁,奇怪的是,格式化它的更好方法是:

    public void process(Foo[] foos) {
        for (final Foo foo : foos) {
            if (foo.needsProcessing()) {
                synchronized (foo) {
                    if (foo.needsProcessing()) {
                        foo.process();  // foo's state may be changed here
                    }
                }
            }
        }
    }

获得同步锁有时可能需要一段时间,如果它被持有,则某些东西正在改变某些东西。这可能是在那段时间里改变了foo的需要处理状态的东西。

如果不需要处理对象,则不想等待锁定。获得锁后,它可能仍不需要处理。因此,即使它看起来有点愚蠢,新手程序员可能倾向于删除其中一个检查,只要foo.needsProcessing()是一个可以忽略不计的功能,它实际上是做到这一点的合理方法。


回到主要问题,当您想要根据数组中的本地值进行同步时,这是因为时间至关重要。在这些情况下,您要做的最后一件事就是锁定数组中的每个项目或处理数据两次。只有当您有几个线程执行一堆工作时,您才会同步本地对象,并且应该很少需要接触相同的数据,但很可能会这样做。

这样做只会在且仅当处理需要处理的 foo 时才会命中锁定,这会导致并发错误。在这些情况下,当您想要仅基于数组中的精确对象进行同步时,您基本上需要双门控语法。这可以防止双重处理 foos 并锁定任何不需要处理的 foo。

您只能遇到非常罕见的情况,即阻塞线程,甚至仅在关键时刻进入锁定,并且您的线程仅在不阻塞会导致并发错误的点上被阻塞。