对局部变量进行同步

时间:2009-11-27 14:03:33

标签: java multithreading synchronization

我有一个多线程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是第三方库的一部分)。

我习惯了没有黄色标记的代码,所以社区的任何建议都值得赞赏。在本地人做同步真的很糟糕吗?有没有替代方案可以适用于我的情况?

提前致谢!

7 个答案:

答案 0 :(得分:21)

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
    }
}

  

在本地人上同步这真的很糟糕吗?

在本地本身上同步也不错。真正的问题是:

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

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

答案 1 :(得分:4)

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。

你会遇到非常罕见的阻塞线程的情况,甚至只是在进入锁定的时候才会这样做,而且只有在没有阻塞会导致并发错误的情况下才会阻塞你的线程。

答案 2 :(得分:2)

IDE应该会帮助你,如果它出错了,你就不应该弯下腰来取悦它。

您可以在IntelliJ中禁用此检查。 (有趣的是它是“线程问题”中默认启用的唯一一个。这是人们犯的最普遍的错误吗?)

答案 3 :(得分:1)

将循环体重构为以foo为参数的单独方法。

答案 4 :(得分:1)

没有自动智能是完美的。我认为你在局部变量上同步的想法非常正确。所以也许按你自己的方式行事(这是正确的)并建议JetBrains他们应该调整他们的检查。

答案 5 :(得分:0)

对集合中的对象进行同步没有任何问题。您可以尝试使用正常for循环替换foreach:

for (int n = 0; n < Foos.length; n++) {
    Foo foo = Foos[n];

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

或甚至(因此探测器不会跳过Foo foo):

for (int n = 0; n < foos.length; n++) {
    if (null != foos[n] && foos[n].needsProcessing()) {
        synchronized (foos[n]) {
            foos[n].process();  // foos[n]'s state may be changed here
        }
    }
}

不使用临时值来阻止多个foos[n]不是最佳做法,但如果它可以防止不需要的警告,您可能会使用它。添加注释为什么此代码具有异常形式。 : - )

答案 6 :(得分:0)

在.NET世界中,对象有时会将其锁定对象作为属性。

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

这允许对象根据其实现给出不同的锁对象(例如,将监视器委托给底层数据库连接或其他东西)。