Java数组线程安全吗?

Posted

技术标签:

【中文标题】Java数组线程安全吗?【英文标题】:Are Java arrays thread safe? 【发布时间】:2021-12-23 08:48:24 【问题描述】:

像标题一样,给定两个数组int[] aint[] b,由两个线程共享,每个线程重新排列两个数组的元素,使第一个数组的每个元素都是对应元素的<=第二个数组a[i] <= b[i] 输出似乎总是正确的,不需要同步

public class MyClass 

    int[] a =  9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ;
    int[] b =  0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ;

    class MyThread extends Thread 
        public void run() 
                for (int i = 0; i < a.length; i++) 
                    if (a[i] > b[i]) 
                        int temp = b[i];
                        b[i] = a[i];
                        a[i] = temp;
                    
                
        
    

    public static void main(String[] args) 

        MyClass myClass = new MyClass();

        MyThread t1 = myClass.new MyThread();
        MyThread t2 = myClass.new MyThread();

        t1.start();
        t2.start();

        while (t1.isAlive() | t2.isAlive()) 
            System.out.println("waiting");
        

        System.out.println(Arrays.toString(myClass.a));
        System.out.println(Arrays.toString(myClass.b));
    

这是我得到的输出(多次运行)我应该认为这只是运气还是我缺少什么?

a = [0, 1, 2, 3, 4, 4, 3, 2, 1, 0]
b = [9, 8, 7, 6, 5, 5, 6, 7, 8, 9]

【问题讨论】:

两个线程都尝试将 same 更改应用于数组。如果每个线程在数组上执行它的特殊操作(例如,一个会相加,另一个会相乘,或者一个会使用&gt; 而另一个会使用&lt; 来排列元素),那么每次运行的结果都会有所不同。 附注:您应该在主线程中使用Thread.join 而不是繁忙循环。此外,| 是位运算符,它与布尔运算符 || 具有不同的语义。 【参考方案1】:

不,他们不是。线程将按照他们想要的方式修改它们,并且有可能出现竞争条件。如果你希望多个线程一次对一个集合进行操作,你需要使用特殊的线程安全集合,它内置了同步,所以你不必担心

您的输出看起来正确的原因是这种竞争条件发生的可能性很小。您无法通过单元测试来保护它。你需要做一些分析工作来证明你的代码是线程安全的,或者使用一个特殊的框架来测试多线程的东西。

这能回答你的问题吗?在 cmets 中给我回复

【讨论】:

是的,我知道这一点,正是由于这个原因,我对始终正确的输出感到惊讶,我认为我缺少一些东西,谢谢你的重播 @user13121591 如果此答案对您有帮助,请随时发表评论和点赞,以便其他人也可以找到并重复使用它。 仅仅因为答案在测试中总是正确的并不意味着没有线程安全错误。这就是让线程安全变得如此困难的部分原因。【参考方案2】:

这是运气。除非线程以某种方式同步,否则不能保证一个线程的写入对其他线程可见。数组也不例外。

这里有一个AtomicIntegerArray 类型可以为您提供帮助。

虽然将数组字段声明为 volatile 不会扩展到数组的元素,但可能有一种方法可以使用它来建立发生前的关系。我必须考虑清楚,但如果它确实有效,那么它会相当脆弱,因为稍微改变用法可能会产生并发错误。

【讨论】:

注意:并发集合,同时确保更改的可见性无助于这里的数据竞争。两个线程可以同时评估同一ia[i] &gt; b[i] 子句,然后尝试执行交换,但结果不可预测,即使每个操作都是原子操作。不幸的是,在这种情况下没有其他选择,只能引入锁来处理每个单独的索引i,或者使用单个锁来同步整个处理逻辑。 @Aivean 我还没有重新编写代码并研究它,但我认为compareAndSet() 能够解决这个问题。 好的,我想了一下,你可能是对的。但这将非常棘手。例如,您必须按以下顺序读取a[i]b[i] 两次:read a, read b, read a, read b,然后检查第一次读取是否与第二次读取匹配,以确保其他线程没有覆盖ab 在读取之间。但是假设所有线程都以相同的顺序写入ab,那么写入应该没有问题,因为第二个线程在写入a时总是会失败CAS。【参考方案3】:

不,数组不是线程安全的。从某种意义上说,这是一种运气,因为它是您不知道的保证。

在 JLS 中:

线程 T1 中的最终操作与另一个线程 T2 中检测到 T1 已终止的任何操作同步。

发现线程因 isAlive 而死掉了。 synchronizes-with 创建了一个happens-before 关系,并确保对数组的更改对主线程可见。两个线程之间存在数据竞争,因此不知道它们的更新应用的顺序,但由于它们在做同样的事情,这并不重要。

有时代码只是偶然地工作,因为某些东西导致了先发生关系,然后那个东西被删除了,东西神秘地中断了。删除对 System.out.println 的调用就是一个例子,该调用获得了一个锁,最终使某些更改可见,然后有人删除了 println 并惊讶于它不再起作用。看到这个问题:Loop doesn't see value changed by other thread without a print statement

这种东西不是你想依赖的。

【讨论】:

以上是关于Java数组线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

java多线程访问同一个数组,存在并发问题吗,每个线程访问的是数组的不同部分,不存在冲突

HashMap多线程不安全问题总结

java priorityblockingqueue 线程安全吗

Java+线程内部调用实例方法会多线程安全吗?

Java有线程安全的set吗?

Java有线程安全的set吗?