Java ArrayList / String / atomic 变量读取线程安全吗?

Posted

技术标签:

【中文标题】Java ArrayList / String / atomic 变量读取线程安全吗?【英文标题】:Is Java ArrayList / String / atomic variable reading thread safe? 【发布时间】:2011-01-24 05:35:29 【问题描述】:

我一直在仔细考虑和阅读,但可以找到绝对权威的答案。

我有几个由包含 ArrayList、字符串和原始值的对象组成的深层数据结构。我可以保证这些结构中的数据不会改变(没有线程会对列表进行结构更改、更改引用、更改原语)。

我想知道在这些结构中读取数据是否是线程安全的;即,从对象中递归读取变量、迭代 ArrayList 等以在不同步的情况下从多个线程中的结构中提取信息是否安全?

【问题讨论】:

I can guarantee that the data in these structures will not change,那么在这种情况下,在不同步的情况下同时读取变量是完全可以的。 【参考方案1】:

它不安全的唯一原因是如果一个线程正在写入一个字段,而另一个线程同时从它读取。如果数据未更改,则不存在 race condition。使对象不可变是保证它们是线程安全的一种方法。首先阅读 IBM 的 this 文章。

【讨论】:

太好了,谢谢。这当然是我的直觉,但想检查没有人知道关于使用非线程安全变量等的迭代器的任何 cotcha。也许我现在可以停止痴迷并编写一些代码 :) 不幸的是,这是错误的。在没有 happens-before 条件的情况下(由语言规范定义),“before”、“while”和“after”之类的词没有意义。使用final 使对象不可变是一个好主意,所以我不会反对,但是可以在没有内存屏障的情况下获得线程安全的想法是非常非常错误的。 @erickson,公平地说,这个问题并没有真正解决这个问题。他询问多个线程是否可以安全地读取数据,如果数据已安全发布(您所了解的),他们可以。所以也许“非常非常错误”有点强烈,也许你的意思是说“是的,只要数据是安全发布的”。 我建议您阅读 Brian Goetz 的一些文章。这个部分有一个部分可能比我能更好地解释这个问题:ibm.com/developerworks/java/library/j-jtp08223/index.html#2.0 是的,这里有一个关于使用 volatile 进行安全发布的好方法:ibm.com/developerworks/java/library/j-jtp06197.html【参考方案2】:

ArrayList 的成员不受任何内存屏障的保护,因此无法保证对它们的更改在线程之间可见。即使对列表进行的唯一“更改”是其构造,这也适用。

任何线程之间共享的数据都需要一个“内存屏障”来确保其可见性。有几种方法可以做到这一点。

首先,在构造函数中声明为final 并在构造函数中初始化的任何成员在构造函数完成后对任何线程都是可见的。

对声明为volatile 的任何成员所做的更改对所有线程都是可见的。实际上,写入从任何缓存“刷新”到主内存,任何访问主内存的线程都可以看到它。

现在它变得有点棘手。线程写入 volatile 变量的之前线程所做的任何写入也会被刷新。同样,当一个线程读取一个 volatile 变量时,它的缓存会被清除,随后的读取可能会从主内存重新填充它。

最后,synchronized 块就像一个易失的读写,增加了原子性的质量。当监视器被获取时,线程的读取缓存被清除。当监视器被释放时,所有的写入都被刷新到主内存中。

完成这项工作的一种方法是让填充共享数据结构的线程将结果分配给volatile 变量(或AtomicReference,或其他合适的java.util.concurrent 对象)。当其他线程访问该变量时,它们不仅可以保证获取该变量的最新值,还可以保证该线程在将值分配给该变量之前对数据结构所做的任何更改。

【讨论】:

“ArrayList 的成员不受任何内存屏障的保护,因此无法保证对它们的更改在线程之间可见”- OP 明确声明数据不会更改。 @Amir Afghani - 首先构造对象是需要其他线程可见的“更改”。这是另一篇关于安全施工的 Brian Goetz 文章:ibm.com/developerworks/java/library/j-jtp0618.html 令人着迷,感谢埃里克森。这正是我害怕的那种陷阱。我在此页面上阅读的模式 #2:ibm.com/developerworks/java/library/j-jtp06197.html 确认了您构建树的建议,然后让读取线程通过 volatile 变量访问它是一个非常好的主意。谢谢! & 更好的是关键点已经存在于我的代码中。这将是一个关键字修复:)【参考方案3】:

如果数据在创建后从未被修改,那么你应该没问题,读取将是线程安全的。

为了安全起见,您可以将所有数据成员设为“最终”,并尽可能让所有访问函数可重入;这可以确保线程安全,并且可以帮助您在将来更改代码时保持代码线程安全。

一般来说,让尽可能多的成员成为“最终”成员有助于减少引入错误,因此许多人提倡将此作为 Java 最佳实践。

【讨论】:

我的回答谈到了最后 7 分钟前:-) 像 Amir Afghani 一样,数据可以在没有内存屏障的情况下跨线程共享的想法是错误和危险的。但是,也像 Amir 一样,您对 final 有一个好主意。 但是Erickson,我并没有说在没有内存障碍的情况下跨线程共享数据没有数据不变的条件是可以的。请解释这有多危险。 @Erickson:我也和 Amir 有同样的问题。 ;-) 为什么它很危险? 信息量最大的解释是 Java 语言规范第 17 章 (java.sun.com/docs/books/jls/third_edition/html/memory.html)。但 Brian Goetz 的总结 (ibm.com/developerworks/java/library/j-jtp0618.html#1c) 可能更中肯。请注意,在他的示例中,“数据没有变化”,但仍然不安全。【参考方案4】:

使用java.util.Vector,如果它们确实不可修改,则使用java.util.Collections.unmodifiableXXX() 包装器,这将保证它们不会更改,并将执行该合同。如果要修改它们,请使用java.util.Collections.syncronizedXXX()。但这只能保证内螺纹的安全。使变量final 也将有助于编译器/JIT 进行优化。

【讨论】:

【参考方案5】:

我看不出使用多个线程从 ArrayList、字符串和原始值中读取会有什么问题。

只要您只是阅读,就不需要同步。对于字符串和原语,它肯定是安全的,因为它们是不可变的。对于 ArrayLists 应该是安全的,但我没有授权。

【讨论】:

Stings 在内部是不可变的,但是变量可以从你下面改变出来,你应该说明的是指向 String 和 primatives 的变量应该被标记为'final'。【参考方案6】:

就像其他人的答案一样:如果您确定需要同步您的数组列表,您可以调用 Collections.synchronizedList(myList) 它将返回一个线程安全的实现。

【讨论】:

在某些极端情况下,这并不能真正保证线程安全。什么是 2 个线程,每个线程检查列表的大小,然后删除一些东西? @Chandru 这种原子性需要同步块,但它只能由 synchronizedList() 包装器保证;试图直接用ArrayList 做同样的事情是不可靠的。

以上是关于Java ArrayList / String / atomic 变量读取线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

java中char类型的Arraylist如何转化成一个string?

ArrayList<HashMap<String,String>> 到 JSON 对象 Java

JAVA这样的定义:Collection<String>n=new ArrayList<String>()与List<String>n=new ArrayList(

Java中数组的ArrayList排序

java arraylist怎么转化成数组

java Map<String,ArrayList<String>> 如何转化为Map<String,List<String>>?