【问题标题】:In Java, is it required to synchronize write access to an array if each thread writes to a separate cell space?在Java中,如果每个线程写入单独的单元格空间,是否需要同步对数组的写入访问?
【发布时间】:2011-08-28 23:24:46
【问题描述】:

如果每个线程写入单独的单元格空间,是否需要同步对Java中数组的写入访问?

编辑: 具体来说,数组要么是原始数组,要么是不可变对象数组。前任。一个 int 数组或一个 String 数组。

【问题讨论】:

  • 这个问题似乎相当具体,尽管数组的类型可能是相关的。如果它是一个对象数组,请说明您的意思是覆盖引用 (array[1] = new Foo(bar)),还是改变对象 (array[1].setBar(bar))。 @sthupahsmaht,您的评论更笼统。
  • +1 就问题的特殊性向 Matthew 表示:这是一个定义明确的问题。
  • java array thread-safety 的可能重复项
  • 不,不是。在另一个问题中,线程没有单独的单元格空间。

标签: java arrays multithreading thread-safety


【解决方案1】:

不,不需要同步。

定义在JLS §17.6 Word Tearing:

Java 虚拟机的一个实现考虑是每个字段和数组元素都被认为是不同的;对一个字段或元素的更新不得与任何其他字段或元素的读取或更新交互。特别是,两个线程分别更新一个字节数组的相邻元素,不能相互干扰或交互,也不需要同步来保证顺序一致性。

【讨论】:

  • 很好,这应该意味着在某些架构上对数组的操作效率低下,除此之外
  • 这不是您的参考所说的:有些操作不受数据竞争的影响,但这并不是所有的数组操作。
  • @Charlie:“每个字段和数组元素都被认为是不同的;对一个字段或元素的更新不得与任何其他字段或元素的读取或更新交互。”。这意味着 array[1] 和 array[2] 可以被视为两个完全独立的变量,所以我认为这回答了 OP 的问题
  • @akappa 我不是架构专家。然而,最近在 GCC 邮件列表上讨论了关于 C++0x 内存模型的讨论,这个帖子令人鼓舞:permalink.gmane.org/gmane.comp.gcc.patches/232708
  • @bkail:我仍然认为你不能在没有同步的情况下安全地操作 longdouble
【解决方案2】:

如果读取访问也以相同的方式进行分区,则不需要按照 bkail 的链接进行同步。

但如果线程读取彼此的写入,仍然需要memory barrier 来强制同步缓存内容。否则,threadys 可能会得到不一致的读取结果。

【讨论】:

    【解决方案3】:

    不是一个简单的是或否问题。需要考虑的一些事项:

    • 您的问题暗示您将原始类型(或引用)存储在数组中。对存储在数组中的对象执行复杂的操作可能需要同步。
    • 在不同步的情况下更改 longdouble不安全
    • 即使您存储 不是 doublelong 的原语,也有可能由于缓存,其他线程无法立即看到更改的值(导致 陈旧的阅读

    【讨论】:

    • 第一点是真的,第二点不是。确实,更改 long/double 不是原子,但如果该值仅由一个线程读取,则无关紧要。
    • @bkail:你确定吗?如果写入线程在中断时写入long 的一个32 位字怎么办?然后读取线程将读取损坏的值,不是吗?
    • 如果单个单元格涉及多个线程,那么我同意需要同步,但这不是问题陈述。
    • @Angel 我不否认数据不一致。我声称没有人阅读它并不重要。换句话说,如果原发帖人说,“从主线程,产生 10 个线程,将它们全部连接,然后查看结果;从每个线程,写入一个唯一的单元格”,那么根本没有关系一个线程在“写入中”被中断,因为没有人会看到中间值。
    • @Angel 我对一个简单问题给出了一个简单的答案:根据 JVM 规范,对单独数组单元的并发写访问是安全的。我根据问题的措辞假设原始海报不是新手。
    【解决方案4】:

    如果您希望其他线程始终能够看到您写入的最后一个值,您通常需要进行同步。但是我可能会在某些情况下,例如,预先计算一个非常昂贵的 long[] (每个条目会占用大量 CPU)并且你会从多个线程中执行此操作,知道这些线程中的任何一个都不应写入同一个单元格。然后,一旦完成所有线程,您将使用一次同步以确保每次后续读取都会看到正确的值。类似的东西。

    请注意,如果您不想自己处理同步问题,您可能需要研究像 AtomicLongArray 这样的类。作为一个额外的好处,像 AtomicLongArray 这样的类可能会得到一个无法在 100% Java 中重新创建的实现的支持。

    【讨论】:

      【解决方案5】:

      相同的线程是否也在读取该值。如果是这样,那么您就可以了。如果没有,那么您需要担心其他线程是否看到最新的值。这通常通过关键工作 volatile 或通过原子变量来处理。

      For example if you have an int [] intArray with three elements.
      
      thread 1 updates intArray[0]=<new value>
      thread 2 updates intArray[1]=<new value>
      thread 3 updates intArray[2]=<new value>
      

      如果这些线程也用于读取值,那么你没问题,但如果一个新线程 --> 线程 4 尝试读取值,则不能保证它会看到最新的分配。

      如果你使用 AtomicIntegerArray、AtomicLongArray 或 AtomicReferenceArray 就可以了

      【讨论】:

      • 又一次有人在没有发表评论的情况下对其投了反对票,我发现没有人再次批准并支持它。
      【解决方案6】:

      您可以按照您的要求做,更新每个索引保存的内容,但您不能保证读取索引中数据的其他线程看到的是当前数据。

      Java 中有一个名为volatile 的关键字,用于标记实例和类变量,以便 JVM 知道这些值会发生变化并且不会进行任何读取缓存优化因为读取线程可能会获得陈旧数据。由于您无法标记数组索引内容volatile,因此其他读者可以获取陈旧数据。

      在 Java 中使用原始 Arrays 只是在非常特定的场景中的好习惯。您的情况可能是其中一种情况,但我无法从问题中看出,因此如果您不想重复,我建议您查看java.util.concurrent,特别是CopyOnWriteArrayListCopyOnWriteArraySet

      这些现在是标准库的一部分是有原因的,如果您正在执行繁重的线程和数据共享,您应该尽可能熟悉java.util.concurrent

      【讨论】:

      • 谁反对这个?为什么?据我所知,Jarrod 就在这里。
      【解决方案7】:

      向并发兴趣邮件列表提出的类似问题 - “Atomic byte[] operations?”。

      大卫·福尔摩斯说:

      读/写数组的不同区域应该像 访问独立数组。这就是“无话不说” 保证。无论虚拟机如何实现阵列,虚拟机 必须确保这适用于基本的数组访问。

      正如我所说,如果原生版本的 arraycopy 参与。

      【讨论】:

      【解决方案8】:

      不要直接写入共享资源。你会让多线程程序的性能比它的单线程计数器部分更差。缓存中的垃圾效果,因为缓存在块中被提取和失效。如果您想了解更多信息,请查看此演讲。使用 volatile 和 Synchronize 它比什么都不使用要好,但仍然很慢。

      https://www.youtube.com/watch?v=VCattsfHR4o
      

      【讨论】:

        【解决方案9】:

        我正要添加一个关于这个主题的问题。我做了一个测试,在访问数组时强制争用。既可以控制协调访问,也可以使用非同步的读取-修改-写入操作,如索引--。

        使用索引时--一些计数器> 1(都应该为1,否则互斥中断)

        public class App {
        
        
        static int threads = 1000;
        static int maxIndex = 1000;
        
        public static void main(String[] args)
        {
            try {
                testThreads();
            } catch (InterruptedException ex) {
                Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
            }
            return;
        
        }
        
        public static void testThreads() throws InterruptedException {
        
            AtomicInteger[] ids = new AtomicInteger[maxIndex];
            for (int i = 0; i < maxIndex; i++) {
                ids[i] = new AtomicInteger(0);  
            }
            Executor exec = Executors.newFixedThreadPool(threads);
            AtomicInteger index = new AtomicInteger(maxIndex);
            final CountDownLatch startGate = new CountDownLatch(1);
            final CountDownLatch endGate = new CountDownLatch(threads);
        
            for (int i = 0; i < threads; i++) {
                exec.execute(new Runnable() {
        
                    @Override
                    public void run() {
                        try {
                            startGate.await();
                            try {
                                int i = maxIndex;
                                while (i > 0) {
                                    /** 
                                     * Interchanging this lines force or avoid collisions. 
                                     */
                                 // i = --maxIndex;
                                    i = index.decrementAndGet();
                                    ids[i].incrementAndGet(); 
                                }
                            } catch(Exception ignored) {
                                System.out.println(ignored);
                            } finally {
                                endGate.countDown();
                            }
                        } catch (InterruptedException ignored) {
                        }
                    }
                });
            }
            startGate.countDown();
            endGate.await();
            System.out.println(new ArrayList(Arrays.asList(ids)));
        }
        }
        

        【讨论】:

          【解决方案10】:

          一般来说,是的。试试 Vector。

          【讨论】:

          • Vector 出于所有实际目的已弃用java.util.concurrent 包中有更好的替代方案。任何使用 VectorHashtable 的建议都是糟糕的建议。
          • @Charlie Martin:36K 代表并建议过去的课程?九十年代打电话说他们想要他们的无能,跛脚,上课还是什么!? ;)
          • 打鼾。是的,当一个简单的旧类工作正常时,让我们使用新的复杂类。离开我的草坪。
          • 不建议使用@deprecated 的类;D 在非线程环境中 java.util.ArrayList 明显更快...
          • 我同意。然而,由于 Vector 未被弃用(download.oracle.com/javase/6/docs/api/java/util/Vector.html 并注意到 Jarrod 说“出于所有实际目的”,意思是“但我不喜欢它”)你的建议是错误的。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-14
          • 2021-05-15
          • 1970-01-01
          • 1970-01-01
          • 2021-08-13
          相关资源
          最近更新 更多