Java writer的并发对象优先于reader

Java writer的并发对象优先于reader,java,concurrency,Java,Concurrency,我正在寻找一个并发对象,它可以在以下用例中提供帮助: 线程/实体:1个发布服务器(唯一),0个读卡器 publisher频繁/不稳定地更新数据结构,需要以最小的延迟快速更新 每个读卡器都具有对数据结构的读访问权限(或者通过不允许写入的内容,或者因为读卡器隐式承诺不更改数据) 每个读者都愿意反复尝试访问数据结构,只要它能够检测出发布者何时来并更改数据结构,因为它知道它最终会有足够的时间来阅读它所需要的内容 有什么建议吗?我可以使用a,但有点担心阻止发布者。我宁愿让出版商毁掉读者的阅读机会,也不

我正在寻找一个并发对象,它可以在以下用例中提供帮助:

  • 线程/实体:1个发布服务器(唯一),0个读卡器
  • publisher频繁/不稳定地更新数据结构,需要以最小的延迟快速更新
  • 每个读卡器都具有对数据结构的读访问权限(或者通过不允许写入的内容,或者因为读卡器隐式承诺不更改数据)
  • 每个读者都愿意反复尝试访问数据结构,只要它能够检测出发布者何时来并更改数据结构,因为它知道它最终会有足够的时间来阅读它所需要的内容
有什么建议吗?我可以使用a,但有点担心阻止发布者。我宁愿让出版商毁掉读者的阅读机会,也不愿让读者阻碍出版商

发布者线程:

 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();
      }
 }
读卡器线程:

 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个线程可以写入/读取,无需进一步同步等

从链接的文档:

 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) { ... }
 }

嗯。。。我想我的绊脚石是围绕共享数据结构本身。。。我一直在用类似的东西

 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 */
 }
公共类LotsOfData
{
枚举字段{FEE,FI,FO,FUM};
Map feeFiFoFum=新的EnumMap();
long[]其他=新long[123];
/*其他领域也一样*/
}
然后将更改项发布到队列中,如feeFiFoFum字段的(FEE,23),以及
其他
数组的(331234567l)。(出于性能原因,Bean类型的反射几乎肯定是不存在的。)

尽管如此,看起来我还是被出版商编写它想要的任何东西的简单性所宠坏了,并且知道读者(最终)会有时间进入并获得一组一致的数据,只要它有一个标志,它就可以用来告诉数据是否被修改了

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

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

因此,创建每个变异对象的平均时间为230nsec,将每个变异对象排队/出列到生产者队列中并在消费者中完成(与对象创建和队列操作相比,执行基本类型变异的时间似乎可以忽略不计)。我想还不错,它给了我一些指标来估计这种方法的性能成本。

我会让一个消费者获取数据,合并数据,然后将其传递给多个下游消费者(可能通过其他队列?)