我有一个多线程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是第三方库的一部分)。
我习惯了没有黄色标记的代码,所以社区的任何建议都值得赞赏。在本地人做同步真的很糟糕吗?有没有替代方案可以适用于我的情况?
提前致谢!
答案 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)
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
}
}
这允许对象根据其实现给出不同的锁对象(例如,将监视器委托给底层数据库连接或其他东西)。