【问题标题】:How to prevent nested synchronized blocks when iterating over a collection迭代集合时如何防止嵌套同步块
【发布时间】:2011-02-26 18:19:02
【问题描述】:

在多线程 Java 应用程序中,我需要遍历对象集合。由于集合和对象都可以在我迭代它们时被另一个线程修改,所以我需要使用同步。

但是不推荐嵌套同步块,因为它们可能导致死锁。我该如何解决这个问题?

Collection<Data> dataCollection = something.getDataCollection();

synchronized ( dataCollection ) {
  for ( final Data data : dataCollection ) {
    synchronized ( data ) {
      data.doSomething();  // doSomething() changes object state
    }
  }
}

【问题讨论】:

  • 您能否详细说明为什么需要同步您的集合,即您的数据成员是否相互依赖,或者您是否有某种逻辑可以同时查看所有成员并且无法处理添加还是删除?
  • 新数据实例可以被另一个线程添加到集合中。

标签: java multithreading synchronization nested


【解决方案1】:

我认为您可以使用CopyOnWriteArrayList 代替外部同步。

ArrayList 的线程安全变体,其中所有可变操作(添加、设置等)都是通过制作底层数组的新副本来实现的。 这通常成本太高,但当遍历操作的数量远远超过突变时,它可能比替代方法更有效,并且在您不能或不想同步遍历但需要排除并发线程之间的干扰时很有用

【讨论】:

    【解决方案2】:

    您可以获取集合的副本,并且一次只锁定一个对象。

    Collection<Data> dataCollection = something.getDataCollection();
    Collection<Data> copy;
    synchronized ( dataCollection ) {
      copy = new ArrayList<Data>(dataCollection);
    }
    
    for (Data data : copy) {
        synchronized ( data ) {
          data.doSomething();  // doSomething() changes object state
        }
    }
    

    【讨论】:

      【解决方案3】:

      不敢相信没有人指出避免在 Data 对象上同步的首要方法是让这个对象本身是线程安全的!这也是处理同步的正确方法 - 如果您知道您的对象将被多个线程访问,请按照您认为适合类内部的方式处理同步,而不是在可能访问它的代码中。您当然也会更有效率,因为您可以将同步限制在关键块上,使用 ReadWriteLock、j.u.c.atomic 等

      【讨论】:

        【解决方案4】:

        嵌套同步可能导致死锁,但并非必须如此。避免死锁的一种方法是定义同步对象的顺序并始终遵循它。

        如果您总是在同步数据对象之前同步dataCollection对象,则不会出现死锁。

        【讨论】:

        • 这很好,以防您确定没有人会违反此规定。但是想象一个大项目,30 名开发人员,有人甚至不知道上面的代码,并且可能以错误的顺序进行同步。 => 难以追踪的死锁。
        • @Bozho:您获得锁定的顺序可能应该是系统文档的一部分。如果需要使用这些锁的所有代码都是该大项目中同一个较小模块的一部分,这可能会更容易。
        • 从概念上讲,一旦发生这种死锁,运行时应该很容易检测到这种死锁。我只是不知道是否有 Java 工具可以做到这一点。
        • 有 - 任何制作线程转储的工具。但仍然很难追查到底是什么原因造成的。至于文档和模块——是的,我同意它应该这样工作。但是阅读文档并不多
        【解决方案5】:

        看看ReentrantReadWriteLock。使用此类,您可以实现一个锁,使任意数量的非修改(读取)线程可以同时访问共享属性,但一次只有一个修改(写入)线程访问它(所有其他读取器和写入器被阻塞,直到写线程释放写锁)。请记住彻底测试您的实现,因为锁的错误使用仍然会导致竞争条件和/或死锁。

        【讨论】:

          【解决方案6】:

          您是像 Bozho 所说的那样使用 CopyOnWriteArrayList 还是像 Peter 所说的那样在迭代之前复制列表应该取决于您希望列表被编辑多少而不是迭代。

          当您期望列表的迭代次数远远超过修改次数时,请使用 CopyOnWriteArrayList。

          如果您认为列表的修改量远大于迭代次数,请使用复制列表。

          这些应该是第一个选项,因为除非不可避免,否则并发解决方案应该很简单,但如果这两种情况都不适用,您将需要选择此处的 cmets 中列出的更复杂的策略之一。

          祝你好运!

          【讨论】:

            猜你喜欢
            • 2013-03-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-01-12
            • 2015-09-07
            • 2021-10-22
            • 2010-12-19
            相关资源
            最近更新 更多