以下是线程安全的吗? 4个线程写入相同的数据结构

时间:2014-08-15 16:46:21

标签: java arrays multithreading thread-safety

所以,让我说我有4个线程,它们都循环遍历一个包含100个索引的数组,翻转每个索引中的信息位并写回该索引...

arr[];

Thread 1:

for (int i = 0; i< 100; i+=4) { flip bits of arr[i]}

Thread 2:

for (int j = 1; j< 100; j+=4) { flip bits of arr[j]}

Thread 3:

for (int k = 2; k< 100; k+=4) { flip bits of arr[k]}

Thread 4:

for (int l = 3; l< 100; l+=4) { flip bits of arr[l]}

我是并发的总菜鸟,所以我想知道这是不是很好的做法,还是有另外一种做法?

更新:要清楚 - 如果&#34;翻转arr [i]&#34;和&#34;翻转arr [j]&#34;由于某种原因触摸相同的对象/成员,答案&#34;不是线程安全&#34;很明显(并且与数组或实际问题无关),因此假设这些操作不会直接或在某些更深层次的对象中触及不同i和j对的相同内存。

3 个答案:

答案 0 :(得分:5)

你没有提供足够的细节,以便人们可以给出一个明确的答案,所以我会做一些额外的假设。

线程安全

如果你有:

  1. 主要帖子:int[] arr = new int[100]; //populate array
  2. 为主线程中的每个T1,T2,T3,T4调用t.start(),然后在这些线程中运行循环,确保每个线程在不同的索引子集上运行(在您的情况下就是这种情况)由于for循环中的步骤4而导致的原始代码
  3. 为主线程中的每个T1,T2,T2,T4调用t.join()
  4. 然后你有以下保证:

    • 在步骤2中,T1..T4中的每一个都将访问相同的正确构造的数组 - 这是因为启动一个线程会在数组初始化和循环之间创建一个先发生的关系
    • 每个线程在不同的索引上运行,每个索引可以被视为一个单独的int,因此它们不会相互干扰
    • 此时(步骤2结束),您没有任何可见性保证:例如,T1所做的更改仅保证在T1中可见 - 特别是主线程仍然可以看到其中的数组初始状态
    • 因此,您需要通过同步操作“发布”更改 - 一种方法是加入主线程中的线程

    良好做法

    以下是一些一般性评论。

    正如另一个答案所指出的那样,如果性能受到关注,你应该尝试以更加缓存的方式编写代码。

    如果所有每个线程都执行25个整数的一些简单位操作,则启动一个线程可能比操作本身花费更多时间,并且顺序运行整个事件会更快。如果您已经运行了一个线程池(例如ExecutorService),那么提交任务的开销可能会小到足以提供增益而不是顺序执行。

    如果您乐意支付一些性能开销,您还可以使用java 8的并行流,这样您就可以编写更少的代码:

    IntStream.range(0, arr.length).parallel()
             .forEach(i -> flip(arr[i]));
    

    在任何情况下,你都应该衡量你所做的事情是否明智......

    参考文献:

    • Thread.start() / Thread.join()在关系之前创建一个事件:JLS #17.4.5

        
          
      • 在线程中对start()的调用发生在启动线程中的任何操作之前。
      •   
      • 线程中的所有操作都发生在任何其他线程从该线程上的join()成功返回之前。
      •   
    • 每个数组元素都是独立的:JLS #17.6

        

      特别是,分别更新字节数组的相邻元素的两个线程不得干涉或交互,也不需要同步以确保顺序一致性。

      更确切地说,这是在JLS #17.4.1中定义的,它表示为了多线程的目的,每个数组元素都被视为一个单独的变量:

        

      在本章中,我们使用术语变量来指代字段和数组元素。

答案 1 :(得分:1)

由于其他线程没有触及数组索引,这应该没问题。尽管如此,你仍然有很多虚假的分享。更好的方法是将索引0-24给线程A,25-49给线程B等等。这样可以更加缓存友好。

答案 2 :(得分:0)

我认为你的代码只是伪代码。 (它不是标记的Java代码,所以也许您最好为我们提供真实的示例代码)。但我会按照你的想法去做。

您有一个数据结构arr

你有四个线程同时修改arr

您的代码不是线程安全的。

为了使其线程安全,您可以使用互斥锁(处理并发的技术之一)。

围绕arr包裹互斥锁。这将锁定 arr,只有拥有互斥锁的线程才能访问arr

互斥锁应该在for循环的开头获得,并在for循环结束时释放。

循环将竞争互斥锁,顺序通常是不可预测的。

P.S。:如果您可以断言没有arr元素引用另一个arr元素引用的对象,那么您可能不需要过多关注线程安全性。但最好真的编写线程安全代码。