【问题标题】:Are arrays thread-safe in Java?Java中的数组是线程安全的吗?
【发布时间】:2019-04-23 07:16:15
【问题描述】:

一个线程从数组的一个索引读取,而另一个线程写入数组的另一个索引,是否存在任何并发问题,只要索引不同?

例如(这个例子不一定推荐实际使用,只是为了说明我的观点)

 class Test1
 {
     static final private int N = 4096;
     final private int[] x = new int[N];
     final private AtomicInteger nwritten = new AtomicInteger(0);
     // invariant: 
     // all values x[i] where 0 <= i < nwritten.get() are immutable

     // read() is not synchronized since we want it to be fast
     int read(int index) {
         if (index >= nwritten.get())
             throw new IllegalArgumentException();
         return x[index];
     }
     // write() is synchronized to handle multiple writers
     // (using compare-and-set techniques to avoid blocking algorithms
     // is nontrivial)
     synchronized void write(int x_i) {
         int index = nwriting.get();
         if (index >= N)
             throw SomeExceptionThatIndicatesArrayIsFull();
         x[index] = x_i;
         // from this point forward, x[index] is fixed in stone
         nwriting.set(index+1);
     }     
 }

编辑: 批评这个例子不是我的问题,我实际上只是想知道数组访问一个索引,同时访问另一个索引,是否会造成并发问题,想不出简单的例子。

【问题讨论】:

    标签: java arrays concurrency thread-safety


    【解决方案1】:

    虽然您不会像您提到的那样通过更改数组来获得无效状态,但是当两个线程在没有同步的情况下查看非易失性整数时,您将遇到同样的问题(请参阅Memory Consistency Errors 上的 Java 教程中的部分) .基本上,问题是线程 1 可能会在空间 i 中写入一个值,但不能保证线程 2 何时(或是否)会看到更改。

    java.util.concurrent.atomic.AtomicIntegerArray 类做你想做的事。

    【讨论】:

    • 谢谢... drat,我想使用一个 byte[] 数组,看起来没有这样的原子动物....我想我会使用同步方法并保持简单。
    • 如果您的读取次数多于写入次数,您可能需要查看 java.util.concurrent.locks.ReadWriteLock
    • 只是为了确保我理解:所以如果 CPU 执行顺序是 (1) 线程 A 将 33 写入 x[0],(2) 线程 A 设置一个“安全标志”,表示它是安全的读取 x[0],(3) 线程 B 读取“安全标志”,表示读取 x[0] 是安全的,(4) 线程 B 读取 x[0],即使我使用也不能保证内存传播会发生“安全标志”上的同步;我必须在同步过程中以包括 x[0] 的读/写的方式使用同步?
    • 根据内存一致性属性 (java.sun.com/javase/6/docs/api/java/util/concurrent/…) 文档,如果安全标志在 2 之前始终为假,则 (1) "happens-before" (2) 和 (3) "happens- before” (4) 因为它们在单个线程中,以及 (2) “happens-before” (3) 因为同步。但是,如果您要为每个字节使用锁和布尔值,则最好使用 AtomicIntegerArray,因为您将使用尽可能多的空间(复杂性更低)
    • 另一种方法是将大字节数组拆分为更小的块,但我认为您需要的块数量不会超过核心数量(注意:未经测试)。如果整个阵列上的单个锁(甚至是读写锁)太慢,您可以检查一下。
    【解决方案2】:

    这个例子有很多与散文问题不同的东西。

    该问题的答案是数组的不同元素是独立访问的,因此如果两个线程更改不同的元素,则不需要同步。

    但是,Java 内存模型不保证(据我所知)一个线程写入的值将对另一个线程可见,除非您同步访问。

    根据您真正想要完成的任务,java.util.concurrent 可能已经有一个可以为您完成任务的课程。如果没有,我仍然建议您查看ConcurrentHashMap 的源代码,因为您的代码似乎在做与管理哈希表相同的事情。

    【讨论】:

      【解决方案3】:

      我不确定是否只同步 write 方法,而不同步 read 方法是否可行。并不是所有的后果是什么,但至少它可能导致read 方法返回一些刚刚被write 覆盖的值。

      【讨论】:

        【解决方案4】:

        是的,因为糟糕的缓存交错仍然可能发生在多 CPU/核心环境中。有几种方法可以避免它:

        • 使用 Unsafe Sun 私有库以原子方式设置数组中的元素(或 Java7 中的 jsr166y 添加功能
        • 使用 AtomicXYZ[] 数组
        • 使用带有一个 volatile 字段的自定义对象并拥有该对象的数组。
        • 在您的算法中改用 jsr166y 附录的 ParallelArray

        【讨论】:

          【解决方案5】:

          由于 read() 未同步,您可能会遇到以下情况:

          Thread A enters write() method
          Thread A writes to nwriting = 0;
          Thread B reads from nwriting =0;
          Thread A increments nwriting. nwriting=1
          Thread A exits write();
          

          既然你想保证你的变量地址永远不会冲突,那么像(折扣数组索引问题)这样的东西呢:

          int i;
          synchronized int curr(){ return i; }
          synchronized int next(){ return ++i;}
          
          int read( ) {
                   return values[curr()];
               }
          
          void write(int x){
             values[next()]=x;
          }
          

          【讨论】:

          • 谢谢,但不是我的问题,您的情况不会发生(步骤 2 和 3 永远不会发生)
          猜你喜欢
          • 2021-12-23
          • 2021-12-16
          • 2011-03-07
          • 2010-12-16
          • 2016-08-24
          • 1970-01-01
          • 2013-09-04
          相关资源
          最近更新 更多