writer-taking-precedence-over-reader的并发对象

时间:2009-08-06 13:59:21

标签: java concurrency

我正在寻找一个可以协助以下用例的并发对象:

  • 线程/实体:1个发布者(唯一),0个读者
  • 发布者经常/不正常地更新数据结构,需要快速且延迟最小化
  • 每个读者都具有对数据结构的读访问权(通过不允许写入的内容,或者因为读者隐含承诺不会更改数据)
  • 每个读者都愿意反复尝试访问数据结构,只要它能够检测出版商何时来并改变它,因为它知道它最终会有足够的时间来阅读它所需要的内容。

有什么建议吗?我可以使用ReentrantReadWriteLock,但我有点担心阻止发布商。我宁愿让出版商破坏读者阅读的机会,也不愿让读者能够阻止出版商。

发布商帖子:

 PublisherSignal ps = new PublisherSignal();
 publishToAllReaders(ps.getReaderSignal());

   ...

 while (inLoop())
 {
      ps.beginEdit();
      data.setSomething(someComputation());
      data.setSomethingElse(someOtherComputation());
      ps.endEdit();

      doOtherStuff();
 }

读者主题:

 PublisherSignal.Reader rs = acquireSignalFromPublisher();

      ...

 while (inLoop())
 {
      readDataWhenWeGetAChance();

      doOtherStuff();
 }

      ...

 public readDataWhenWeGetAChance()
 {
      while (true)
      {
           rs.beginRead();
           useData(data.getSomething(), data.getSomethingElse());
           if (rs.endRead())
           {
               // we get here if the publisher hasn't done a beginEdit()
               // during our read.
               break;
           }

           // darn, we have to try again. 
           // might as well yield thread if appropriate
           rs.waitToRead();
      }
 }

编辑在更高级别,我要做的是让发布者每秒更改数据几千次,然后让读者以更慢的速度显示最新更新(5 -10次/秒)。我会使用ConcurrentLinkedQueue来发布更新已发生的事实,除了(a)同一项目可能有数百个更新,我想合并,因为必须重复复制大量数据似乎像废物是一个性能问题,并且(b)有多个读者似乎排除了一个队列...我想我可以有一个主代理阅读器并让它通知每个真正的读者。

2 个答案:

答案 0 :(得分:1)

为什么不使用BlockingQueue

您的发布者可以独立于正在阅读的内容写入此队列。读者(类似地)可以从队列中取出内容而不用担心阻止编写器。线程安全由队列处理,因此2个线程可以写入/读取而无需进一步同步等。

来自链接的文档:

 class Producer implements Runnable {
   private final BlockingQueue queue;
   Producer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while(true) { queue.put(produce()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   Object produce() { ... }
 }

 class Consumer implements Runnable {
   private final BlockingQueue queue;
   Consumer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while(true) { consume(queue.take()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   void consume(Object x) { ... }
 }

答案 1 :(得分:0)

嗯...我想我的绊脚石围绕共享数据结构本身......我一直在使用像

这样的东西
 public class LotsOfData
 {
      int fee;
      int fi;
      int fo;
      int fum;

      long[] other = new long[123];

      /* other fields too */
 }

发布商经常更新数据,但一次只更新一个字段。

听起来好像我应该找到一种以有利于使用生产者 - 消费者队列的方式序列化更新的方法:

 public class LotsOfData
 {
      enum Field { FEE, FI, FO, FUM };
      Map<Field, Integer> feeFiFoFum = new EnumMap<Field, Integer>();

      long[] other = new long[123];

      /* other fields too */
 }

然后将更改项目发布到队列,如(FEE,23)为feeFiFoFum字段,以及(33,1234567L)为other数组。 (出于性能原因,Bean类型的反射几乎可以肯定。)

尽管如此,看起来我只是让出版商写下他们想要的东西,并且知道有时间(最终)让读者进入并得到一致的集合,这似乎让我感到羞愧。数据,如果它只有一个标志,它可以用来判断数据是否已被修改。

更新:有趣的是,我尝试了这种方法,使用一个Muturrent对象的ConcurrentLinkedQueue(只存储1次更改所需的状态),类似于上面的第一个LotsOfData(4个int字段和一个27个longs的数组,以及在大约10000个批次之间使用Thread.sleep(1)产生总共1000万个突变的生产者,以及每100毫秒检查一次队列的消费者,并消耗任何存在的突变。我以多种方式进行测试:

  • 测试框架内的空操作(只循环1000次,调用Thread.sleep(1),并检查是否使用空对象):运行jre6u13的3GHz Pentium 4上1.95秒。
  • 测试操作1 - 仅在生产者端创建和应用突变:4.3秒
  • 测试操作2 - 在生产者端创建并应用突变,在创建时将每个突变放在队列中:12秒

因此创建每个变异对象平均为230nsec,平均为770nsec将每个变异对象排入/出列到生产者的队列中并在消费者中将其拉出(执行原始类型的突变的时间似乎可以忽略不计,与对象创建和队列操作相比,它应该是)。我猜不错,它为我提供了一些估算这种方法的性能成本的指标。