Java数组线程安全吗?
Posted
技术标签:
【中文标题】Java数组线程安全吗?【英文标题】:Are Java arrays thread safe? 【发布时间】:2021-12-23 08:48:24 【问题描述】:像标题一样,给定两个数组int[] a
、int[] 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 更改应用于数组。如果每个线程在数组上执行它的特殊操作(例如,一个会相加,另一个会相乘,或者一个会使用>
而另一个会使用<
来排列元素),那么每次运行的结果都会有所不同。
附注:您应该在主线程中使用Thread.join
而不是繁忙循环。此外,|
是位运算符,它与布尔运算符 ||
具有不同的语义。
【参考方案1】:
不,他们不是。线程将按照他们想要的方式修改它们,并且有可能出现竞争条件。如果你希望多个线程一次对一个集合进行操作,你需要使用特殊的线程安全集合,它内置了同步,所以你不必担心
您的输出看起来正确的原因是这种竞争条件发生的可能性很小。您无法通过单元测试来保护它。你需要做一些分析工作来证明你的代码是线程安全的,或者使用一个特殊的框架来测试多线程的东西。
这能回答你的问题吗?在 cmets 中给我回复
【讨论】:
是的,我知道这一点,正是由于这个原因,我对始终正确的输出感到惊讶,我认为我缺少一些东西,谢谢你的重播 @user13121591 如果此答案对您有帮助,请随时发表评论和点赞,以便其他人也可以找到并重复使用它。 仅仅因为答案在测试中总是正确的并不意味着没有线程安全错误。这就是让线程安全变得如此困难的部分原因。【参考方案2】:这是运气。除非线程以某种方式同步,否则不能保证一个线程的写入对其他线程可见。数组也不例外。
这里有一个AtomicIntegerArray
类型可以为您提供帮助。
虽然将数组字段声明为 volatile
不会扩展到数组的元素,但可能有一种方法可以使用它来建立发生前的关系。我必须考虑清楚,但如果它确实有效,那么它会相当脆弱,因为稍微改变用法可能会产生并发错误。
【讨论】:
注意:并发集合,同时确保更改的可见性无助于这里的数据竞争。两个线程可以同时评估同一i
的a[i] > b[i]
子句,然后尝试执行交换,但结果不可预测,即使每个操作都是原子操作。不幸的是,在这种情况下没有其他选择,只能引入锁来处理每个单独的索引i
,或者使用单个锁来同步整个处理逻辑。
@Aivean 我还没有重新编写代码并研究它,但我认为compareAndSet()
能够解决这个问题。
好的,我想了一下,你可能是对的。但这将非常棘手。例如,您必须按以下顺序读取a[i]
和b[i]
两次:read a, read b, read a, read b
,然后检查第一次读取是否与第二次读取匹配,以确保其他线程没有覆盖a
或b
在读取之间。但是假设所有线程都以相同的顺序写入a
和b
,那么写入应该没有问题,因为第二个线程在写入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多线程访问同一个数组,存在并发问题吗,每个线程访问的是数组的不同部分,不存在冲突