Java 中同步的使用令人困惑:模式还是反模式?
我正在对不属于我的 Java 产品中的更改进行代码审查。我不是 Java 专家,但我强烈怀疑这是毫无意义的,并且表明了对同步工作原理的根本误解。
synchronized (this) {
this.notify();
}
但我可能是错的,因为 Java 不是我的主要游乐场。也许这样做是有原因的。如果您能告诉我开发人员的想法,我将不胜感激。
I'm doing a code review for a change in a Java product I don't own. I'm not a Java expert, but I strongly suspect that this is pointless and indicates a fundamental misunderstanding of how synchronization works.
synchronized (this) {
this.notify();
}
But I could be wrong, since Java is not my primary playground. Perhaps there is a reason this is done. If you can enlighten me as to what the developer was thinking, I would appreciate it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这当然不是毫无意义的,您可以让另一个线程引用包含上述代码的对象,
以便在发生某些情况时被唤醒。不过,在许多情况下,在内部/私有锁对象而不是
this
上进行同步被认为是很好的做法。然而,仅在同步块中执行 .notify() 可能是非常错误的 - 您通常有一些工作要做并在完成时发出通知,在正常情况下也需要以原子方式完成到其他线程。我们必须查看更多代码才能确定它是否真的是错误的。
It certainly is not pointless, you can have another thread that has a reference to the object containing the above code doing
in order to be woken up when something happens. Though, in many cases it's considered good practice to synchronize on an internal/private lock object instead of
this
.However, only doing a .notify() within the synchronization block could be quite wrong - you usually have some work to do and notify when it's done, which in normal cases also needs to be done atomically in regards to other threads. We'd have to see more code to determine whether it really is wrong.
如果这就是同步块中的全部内容,那么它是一个反模式,同步的要点是在块内执行某些操作,设置一些条件,然后调用“通知”或
notifyAll
唤醒一个或多个等待线程。当您使用等待和通知时,您必须使用条件变量,请参阅 此 Oracle教程:
您不应该仅仅因为线程从对 Object#wait 的调用中退出而假设您收到通知,原因有多种:
当调用采用超时值的 wait 版本时,无法知道等待是否由于以下原因而结束 :收到通知或由于超时。
您必须考虑到线程可以在没有收到通知的情况下从等待中唤醒的可能性(“虚假唤醒”)。
收到通知的等待线程仍然必须重新获取它开始等待时放弃的锁,这两个事件没有原子链接;在收到通知和重新获取锁之间的时间间隔内,另一个线程可能会采取行动,并可能更改系统的状态,从而使通知现在无效。
您可能会遇到这样的情况:通知线程在任何线程等待之前执行操作,因此通知无效。假设一个线程在另一个线程发出通知之前进入等待状态是危险的,如果您错了,则等待线程将无限期挂起。
因此,通知本身还不够好,当等待/通知 API 没有提供足够的信息来了解发生了什么情况时,您最终会猜测是否发生了通知。即使通知线程正在执行的其他工作不需要同步,更新条件变量也需要同步;同步块中至少应该更新共享条件变量。
If that is all that is in the synchonized block then it is an antipattern, the point of synchronizing is to do something within the block, setting some condition, then call
notify
ornotifyAll
to wake up one or more waiting threads.When you use wait and notify you have to use a condition variable, see this Oracle tutorial:
You shouldn't assume you received a notification just because a thread exited from a call to Object#wait, for multiple reasons:
When calling the version of wait that takes a timeout value there's no way to know whether wait ended due to receiving a notification or due to timing out.
You have to allow for the possibility that a Thread can wake up from waiting without having received a notification (the "spurious wakeup").
The waiting thread that receives a notification still has to reacquire the lock it gave up when it started waiting, there is no atomic linking of these two events; in the interval between being notified and reacquiring the lock another thread can act and possibly change the state of the system so that the notification is now invalid.
You can have a case where the notifying thread acts before any thread is waiting so that the notification has no effect. Assuming one thread will enter a wait before the other thread will notify is dangerous, if you're wrong the waiting thread will hang indefinitely.
So a notification by itself is not good enough, you end up guessing about whether a notification happened when the wait/notify API doesn't give you enough information to know what's going on. Even if other work the notifying thread is doing doesn't require synchronization, updating the condition variable does; there should at least be an update of the shared condition variable in the synchronized block.
这完全没问题。根据 Java 6
Object#notify()
API 文档:This is perfectly fine. According to the Java 6
Object#notify()
api documentation:如果您仍然想使用内在锁,这通常不是反模式。有些人可能认为这是一种反模式,因为 java.util.concurrent 中的新显式锁粒度更细。
但你的代码仍然有效。例如,当阻塞操作成功并且应该通知另一个等待线程时,可以在阻塞队列中找到此类代码。但请注意,并发问题高度依赖于使用情况和周围的代码,因此您的简单代码片段没有那么有意义。
This is generally not a anti-pattern, if you still want to use intrinsic locks. Some may regard this as an anti pattern, as the new explicit locks from
java.util.concurrent
are more fine grained.But your code is still valid. For instance, such code can be found in a blocking queue, when an blocking operation has succeeded and another waiting thread should be notified. Note however that concurrency issues are highly dependent on the usage and the surrounding code, so your simple snippet is not that meaningful.
对象的 Java API 文档。 notify() 声明该方法“只能由作为该对象监视器所有者的线程调用”。因此,根据周围环境,使用可能是合法的。
The Java API documentation for Object.notify() states that the method "should only be called by a thread that is the owner of this object's monitor". So the use could be legitimate depending upon the surrounding context.