【问题标题】:Java - Should I use a AtomicReferenceArray?Java - 我应该使用 AtomicReferenceArray 吗?
【发布时间】:2017-11-07 16:59:42
【问题描述】:

我陷入了以下情况:

使用大小为 n 的未设置引用 (null) 的数组实例化一个新对象。

构建步骤应该只为每个索引分配一次引用,然后返回完整的数组。

复杂性在于数组由多个线程并行馈送。

我不关心 set 操作的原子性,因为两个线程不能访问同一个索引,但是,我想确保返回填充数组的线程“看到”每个填充的索引。

private final String[] arr = new String[n];

private void parallelFeed() {...} // Multi-threaded

public void build() {
    parallelFeed(arr);
    return arr;
}

我应该使用AtomicReferenceArray 吗?

非常感谢您的帮助

【问题讨论】:

    标签: java concurrency parallel-processing


    【解决方案1】:

    parallelFeed 中,启动您的馈线线程。假定您的馈线线程不重叠,并且每个线程都有自己的范围,以便它们一起完全填充阵列。然后在parallelFeedjoin 线程中。 parallelFeed 将等待其他线程完成。假设没有线程无法完成它的工作,parallelFeed“看到”所有索引都已被填充。

    注意

    由于您在评论中提到“Scala Futures”,请查看 How to wait for several futures 更多关于如何“加入”多个可能发生错误的期货的讨论。

    【讨论】:

    • 感谢您的回复,不知道我应该如何join 线程。我目前正在使用与 Java 非常相似的 Scala 的 Future API。我正在等待期货以Await.ready(Futures.sequence(futures), 1.hour) 完成,你知道这个操作是否加入了线程吗?感谢您的帮助
    • 假设futures 只是Futures 的列表,是的,应该可以工作(在修正错字后,Futures.sequence s/b Future.sequence
    【解决方案2】:

    如果每个线程只处理自己的一组索引,那么您需要做的就是在“返回填充数组的线程”中的所有线程上join,以便等待它们全部运行完毕。不,需要AtomicReferenceArray

    【讨论】:

      【解决方案3】:

      如果您只想保证可见性,那么您实际上需要的是 volatile 关键字。不幸的是,volatiledoes not work well with arrays,你需要稍微解决它。正如该链接所解释的那样,这是可能的,但我不推荐它。

      我会将其实现为Futures 的集合。每个任务都是Future<String[]>,“主”线程会将所有结果编译到最终数组中。见下文。

      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.List;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.Future;
      
      public class Test
      {
          private String[] arr = new String[2];
      
          public Test() throws Exception // You may want to handle the exceptions more
          {                              // gracefully than this
              final int NUM_THREADS = 10;
              ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
      
              // Start the tasks
              List<Future<String[]>> tasks = new ArrayList<>();
      
              tasks.add(
                  executor.submit(() -> partOne())
              );
              tasks.add(
                  executor.submit(() -> partTwo())
              );
      
              // Compile result
              for (Future<String[]> task : tasks)
              {
                  final String[] result = task.get(); // This will wait if necessary
                  for (int i = 0; i < result.length; ++i)
                  {
                      if (result[i] != null) arr[i] = result[i];
                  }
              }
      
              System.out.println(Arrays.toString(arr));
              executor.shutdown();
          }
      
          private static String[] partOne()
          {
              String[] arr = new String[2];
              arr[0] = "Hello";
              return arr;
          }
      
          private static String[] partTwo()
          {
              String[] arr = new String[2];
              arr[1] = "World";
              return arr;
          }
      
          public static void main(String... args) throws Exception
          {
              new Test();
          }
      }
      

      【讨论】:

        【解决方案4】:

        您可以让写入线程在完成填充数组的部分后写入volatile 字段。然后在return arr 之前阅读volatile 字段。由于volatile字段的写入/读取建立happens-before,您将确保数组中数据的可见性。

        以上假设线程之间不需要额外的同步——即,它们不能写入相同的索引,或者类似的东西。

        示例代码:

        public class WriteReadSyncArray<E> {
        
            private final E[] store;
            private volatile boolean sync;
        
            public WriteReadSyncArray(int size) {
                this.store = (E[]) new Object[size];
            }
        
            public void write(int index, E el) {
                store[index] = el;
                sync = true;
            }
        
            public E[] syncAndReadArray() {
                boolean localSync = sync; // establishes happens-before with all previous writes
                return store;
            }
        }
        

        【讨论】:

        • @Michael 链接资源描述的不是我的提议。
        • 那你建议什么?
        • 我刚刚意识到 OP 代码暗示 parallelFeed 应该仅在所有编写者完成后返回,例如,通过使用 join。如果是这种情况,则无需使用volatile — join 也会建立happens-before。
        猜你喜欢
        • 2010-09-13
        • 2011-12-26
        • 2021-12-10
        • 2010-12-25
        • 2014-05-27
        • 2010-12-25
        • 2018-09-11
        • 2012-07-02
        • 2017-04-21
        相关资源
        最近更新 更多