并发设计原则在实践中

时间:2012-03-07 22:49:59

标签: java concurrency synchronization

我有一个Results对象,它由多个线程同时写入。但是,每个线程都有特定的用途并拥有某些字段,因此多个线程实际上不会修改任何数据。在完成所有写入线程的写入之前,此数据的使用者不会尝试读取它。因为我知道这是真的,所以数据写入和读取没有同步。

有一个与此Results对象关联的RunningState对象,用于协调此工作。它的所有方法都是同步的。当一个线程完成它对这个Results对象的工作时,它会调用RunningState对象上的done(),它执行以下操作:递减计数器,检查计数器是否已经变为0(表示所有写入器都已完成),以及如果是这样,将此对象放在并发队列上。该队列由ResultsStore使用,该结果读取所有字段并将数据存储在数据库中。在读取任何数据之前,ResultsStore调用RunningState.finalizeResult(),它是一个空方法,其唯一目的是在RunningState对象上进行同步,以确保读者可以看到所有线程的写入。

以下是我的担忧:

1)我相信这会正常工作,但我觉得我违反了良好的设计原则,没有对多个线程共享的对象的数据修改进行同步。但是,如果我要添加同步和/或拆分,以便每个线程只看到它负责的数据,它会使代码复杂化。修改此区域的任何人都可以更好地了解在任何情况下发生了什么,或者他们可能会破坏某些内容,因此从维护的角度来看,我认为更简单的代码以及解释其工作原理的良好评论是一种更好的方法去吧。

2)我需要调用这种无操作方法这一事实似乎表明设计错误。是吗?

意见表示赞赏。

3 个答案:

答案 0 :(得分:3)

这看起来大概是正确的,如果有点脆弱(例如,如果你改变一个字段的线程本地特性,你可能会忘记同步它并最终得到难以跟踪的数据竞争)。

值得关注的重要领域是记忆能见度;我认为你没有建立它。空finalizeResult()方法可能是同步的,但是如果编写器线程也没有同步它同步的任何内容(大概是this?),那么之前没有发生关系。请记住,同步不是绝对的 - 您相对于同一对象上同步的其他线程进行同步。你的无所事事的方法确实无能为力,甚至不能确保任何内存障碍。

你需要在每个进行写操作的线程和最终读取的线程之间建立一个先发生的关系。没有同步的一种方法是通过volatile变量或AtomicInteger(或其他原子类)。

例如,每个编写器线程都可以在对象上调用counter.incrementAndGet(1),然后读取线程可以检查counter.get() == THE_CORRECT_VALUE。正在写入的易失性/原子字段与正在读取的字段之间存在关系,这为您提供了所需的可见性。

答案 1 :(得分:2)

您的设计是合理的,但如果您使用的是真正的并发队列,则可以改进它,因为来自java.util.concurrent包的并发队列已经确保在将项目放入队列之间的关系之前发生,并且将一个项目取出的线程,这样就不需要在获取线程中调用finalizeResult()(所以不需要那个“什么都不做”的方法调用)。

来自java.util.concurrent包描述:

  

java.util.concurrent及其子包中所有类的方法   将这些保证扩展到更高级别的同步。在   特别是:

     
      
  • 在将对象放入任何对象之前的线程中的操作   并发收集发生在访问之后的操作之前   或从另一个帖子中的集合中删除该元素。
  •   

关于使用AtomicInteger而不是同步的另一个答案中的注释也是明智的(因为使用AtomicInteger进行线程计数可能比同步更好),只需确保在原子减量后得到计数值(例如,redumentAndGet())在与0比较时,以避免两次添加到队列。

答案 2 :(得分:0)

你所描述的确实是安全的,但坦率地说,这听起来也很脆弱(正如你所注意到的)维护可能会成为一个问题。如果没有示例代码,很难说什么是真正最容易理解,所以一个已经主观的问题变得坦白无法回答。你能问一位同事进行代码审查吗? (特别是那个可能不得不处理这种模式的人。)我会相信你,这确实是最简单的方法,但是做一些像写synchronized封锁写入的东西会增加现在和将来的安全性。也就是说,你显然比我更了解你的代码。