局部变量同步
我有一个多线程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 是第 3 方库的一部分)。
我已经习惯了没有黄色标记的代码,因此感谢社区的任何建议。在本地进行同步真的那么糟糕吗?有没有适合我的情况的替代方案?
提前致谢!
I have a multithreaded Java code in which:
- several threads read stateful objects from the synchronized shared storage (i.e. as a result of this, some threads may reference the same objects);
- each thread then invokes a method
process()
and passes its object there; process()
processes objects in some way, which may result changing the objects state;- these state changes should be synchronized.
I've created a method like that:
public void process(Foo[] foos) {
for (final Foo foo : foos) {
if (foo.needsProcessing()) {
synchronized (foo) {
foo.process(); // foo's state may be changed here
}
}
}
}
To the best of my knowledge, this looks legit. However, IntelliJ's inspection complains about synchronizing on the local variable because "different threads are very likely to have different local instances" (this is not valid for me, as I'm not initializing foos in the method).
Essentially what I want to achieve here is the same as having method Foo.process() synchronized (which is not an option for me, as Foo is a part of 3rd party library).
I got used to the code without yellow marks, so any advice from the community is appreciated. Is this really that bad to do synchronization on locals? Is there an alternative which will work in my situation?
Thanks in advance!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我认为上面的片段中存在竞争条件,可能会导致 foo.process() 偶尔在同一个对象上被调用两次。应该是:
在本地同步本身也不错。真正的问题是:
不同的线程是否在正确的对象上同步以实现正确的同步,以及
是否还有其他问题同步这些对象可能会导致问题。
I think there is a race condition in the above fragment that could result in
foo.process()
occasionally being called twice on the same object. It should be:It is not bad to synchronize on locals per se. The real issues are:
whether the different threads are synchronizing on the correct objects to achieve proper synchronization, and
whether something else could cause problems by synchronizing on those objects.
Stephen C 的答案有问题,他毫无意义地输入了很多同步锁,奇怪的是,格式化它的更好方法是:
获取同步锁有时可能需要一段时间,如果它被持有,某些东西就会改变某些东西。可能是当时 foo 的需要处理状态发生了变化。
如果不需要处理该对象,则不想等待锁定。获得锁后,它可能不再需要处理。因此,尽管它看起来有点愚蠢,而且新手程序员可能倾向于删除其中一项检查,但只要 foo.needsProcessing() 是一个可忽略的函数,它实际上是执行此操作的合理方法。
回到主要问题,当您想要基于数组中的本地值进行同步时,因为时间至关重要。在这些情况下,您最不想做的就是锁定数组中的每个项目或处理数据两次。如果您有几个线程正在执行大量工作,并且很少需要接触相同的数据,但很有可能,那么您只会同步本地对象。
只有当且仅当处理需要处理的 foo 会导致并发错误时,这样做才会遇到锁。当您想要仅基于数组中的精确对象进行同步时,您基本上会需要双门语法。这可以防止双重处理 foo 并锁定任何不需要处理的 foo。
您会遇到非常罕见的情况,即阻塞线程,甚至仅在重要的时候输入锁,并且您的线程仅在不阻塞会导致并发错误的情况下被阻塞。
Stephen C's answer has problems, he's entering a lot of synchronization locks pointlessly, oddly enough the better way to format it is:
Getting the synchronization lock can sometimes take a while and if it was held something was changing something. It could be something changed the needing-processing status of foo in that time.
You don't want to wait for the lock if you don't need to process the object. And after you get the lock, it might not still need processing. So even though it looks kind of silly and novice programmers might be inclined to remove one of the checks it's actually the sound way to do this so long as foo.needsProcessing() is a negligable function.
Bringing this back to the main question, when it is the case that you want to synchronize based on the local values in an array it's because time is critical. And in those cases the last thing you want to do is lock every single item in the array or process the data twice. You would only be synchronizing local objects if you have a couple threads doing a bunch of work and should very rarely need to touch the same data but very well might.
Doing this will only ever hit the lock if and only if, processing that foo that needs processing would cause concurrency errors. You basically will want double gated syntax in those cases when you want to synchronize based on just the precise objects in the array as such. This prevents double processing the foos and locking any foo that does not need processing.
You are left with the very rare case of blocking the thread or even entering a lock only and exactly when it matters, and your thread is only blocked for at exactly the point where not blocking would lead to concurrency errors.
IDE 应该帮助你,如果它出错了,你不应该弯腰去讨好它。
您可以在 IntelliJ 中禁用此检查。 (有趣的是,这是“线程问题”下默认启用的唯一一个。这是人们最常犯的错误吗?)
IDE is supposed to help you, if it makes a mistake, you shouldn't bend over and please it.
You can disable this inspection in IntelliJ. (Funny it's the only one enabled by default under "Threading Issues". Is this the most prevalent mistake people make?)
将循环体重构为一个单独的方法,以 foo 作为参数。
Refactor the loop-body into a separate method taking
foo
as parameter.没有哪一种自动智能是完美的。我认为你同步局部变量的想法是非常正确的。因此,也许按照你的方式去做(这是正确的),并建议 JetBrains 他们应该调整他们的检查。
No auto-intelligence is perfect. I think your idea of synchronizing on the local variable is quite correct. So perhaps do it your way (which is right) and suggest JetBrains that they should tune their inspection.
集合中对象的同步没有任何问题。您可以尝试用普通的 for 循环替换 foreach:
或者甚至(这样检测器就不会被
Foo foo
绊倒):不使用临时值来防止多个
foos[n ]
不是最佳实践,但如果它可以防止不必要的警告,您可能会接受它。添加注释为什么此代码具有异常形式。 :-)There is nothing wrong with your synchronisation on an object within a collection. You might try replacing the foreach with a normal for loop:
or even (so the detector won't trip over the
Foo foo
):Not using a temporary value to prevent the multiple
foos[n]
isn't best practice, but if it works to prevent the unwanted warning you might live with it. Add a comment why this code has a deviant form. :-)在 .NET 世界中,对象有时将其锁对象作为属性。
这允许对象根据其实现(例如,将监视器委托给底层数据库连接或其他东西)给出不同的锁对象。
In the .NET world objects sometimes carry their lock object as property.
This allows the object to give out a different lock object, depending on its implementation (e.g. delegating the monitor to a underlying database connection or something).