【问题标题】:Confusing use of synchronized in Java: pattern or anti-pattern?在 Java 中混淆使用同步:模式还是反模式?
【发布时间】:2011-11-28 14:34:06
【问题描述】:

我正在对不属于我的 Java 产品的更改进行代码审查。我不是 Java 专家,但我强烈怀疑这是毫无意义的,并且表明对同步的工作原理存在根本性的误解。

synchronized (this) {
    this.notify();
}

但我可能是错的,因为 Java 不是我的主要游乐场。也许这样做是有原因的。如果您能告诉我开发人员的想法,我将不胜感激。

【问题讨论】:

  • 我认为这更适合codereview.stackexchange.com
  • 您是否想知道在调用notify() 之前是否需要同步?线程在调用notify() 时必须持有对象的监视器,因此通常需要这样做。
  • 啊,不知道codereview。谢谢。
  • 感谢克莱莫尔的链接。它提供了比我想象的更多的上下文。

标签: java multithreading synchronization synchronized


【解决方案1】:

如果这就是同步块中的全部内容,那么它是一个反模式,同步的目的是在块内做一些事情,设置一些条件,然后调用notify或@987654323 @ 唤醒一个或多个等待线程。

当您使用等待和通知时,您必须使用条件变量,请参阅this Oracle tutorial:

注意:始终在测试等待条件的循环内调用等待。不要假设中断是针对您正在等待的特定条件,或者条件仍然为真。

您不应该仅仅因为线程退出对 Object#wait 的调用而假设您收到了通知,原因有很多:

  • 当调用具有超时值的等待版本时,无法知道等待是由于收到通知还是由于超时而结束。

  • 您必须允许线程在未收到通知的情况下从等待中唤醒(“虚假唤醒”)。

  • 收到通知的等待线程仍然需要重新获取它在开始等待时放弃的锁,这两个事件没有原子链接;在被通知和重新获得锁之间的时间间隔内,另一个线程可以采取行动并可能改变系统的状态,因此通知现在无效。

  • 您可能会遇到这样的情况,即通知线程在任何线程等待之前进行操作,从而使通知无效。假设一个线程在另一个线程通知之前进入等待是危险的,如果你错了,等待线程将无限期挂起。

所以通知本身是不够的,当等待/通知 API 没有为您提供足够的信息来了解正在发生的事情时,您最终会猜测是否发生了通知。即使通知线程正在执行的其他工作不需要同步,更新条件变量也需要;至少应该更新同步块中的共享条件变量。

【讨论】:

  • 我是这么想的,但是好像有时候你可能想同步同步。
  • 这绝对不是真的。也许在通知之前所做的事情不需要同步。粒度同步没有害处,这通常是一件好事。一个简单的示例可能是等待用户单击按钮的后台线程(通过等待对象)。当 Swing 通知 ActionListener 时,它可以使用上面的代码执行通知。
  • @Mark 如果在没有保护的情况下更新条件,这听起来很危险;检查条件的另一个线程可以看到不一致的状态。
【解决方案2】:

当然不是没有意义,你可以让另一个线程引用包含上述代码的对象

synchronized(foo) {
    foo.wait();
}

为了在发生事情时被唤醒。不过,在许多情况下,在内部/私有锁对象而不是 this 上同步被认为是一种好习惯。

但是,仅在同步块中执行 .notify() 可能是非常错误的 - 您通常需要完成一些工作并在完成时通知,在正常情况下也需要完成原子地相对于其他线程。我们必须查看更多代码来确定它是否真的是错误的。

【讨论】:

  • 回想起来似乎很明显。现在我需要弄清楚这一切是如何运作的。
  • 好吧,我找不到他在哪里调用 this.wait(),所以我发送了反馈,询问这是否真的有必要。
【解决方案3】:

这很好。根据Java 6 Object#notify() api documentation:

这个方法只能被这个对象的监视器的所有者线程调用。

【讨论】:

  • "Fine" 与 API 的正确使用一样。但是 OP 正在询问它是 pattern 还是 antipattern。
【解决方案4】:

Object.notify() 的 Java API 文档声明该方法“只能由作为该对象监视器所有者的线程调用”。因此,根据周围的环境,使用可能是合法的。

【讨论】:

    【解决方案5】:

    如果您仍想使用内部锁,这通常不是反模式。有些人可能认为这是一种反模式,因为来自 java.util.concurrent 的新显式锁更细粒度。

    但您的代码仍然有效。例如,这样的代码可以在阻塞队列中找到,当阻塞操作成功并且应该通知另一个等待线程时。但是请注意,并发问题高度依赖于用法和周围的代码,因此您的简单 sn-p 没有那么有意义。

    【讨论】:

      猜你喜欢
      • 2020-12-04
      • 1970-01-01
      • 2012-12-09
      • 1970-01-01
      • 2017-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多