Java ArrayList / String / 原子变量读取线程安全吗?
我一直在考虑这个问题阅读却能找到绝对权威的答案。
我有几个由包含 ArrayList、字符串和字符串的对象组成的深层数据结构。原始值。我可以保证这些结构中的数据不会改变(没有线程会对列表进行结构更改、更改引用、更改原语)。
我想知道读取这些结构中的数据是否是线程安全的;即,从对象中递归读取变量、迭代 ArrayList 等以在不同步的情况下从多个线程中的结构中提取信息是否安全?
I've been mulling this over & reading but can find an absolute authoritative answer.
I have several deep data structures made up of objects containing ArrayLists, Strings & primitive values. I can guarantee that the data in these structures will not change (no thread will ever make structural changes to lists, change references, change primitives).
I'm wondering if reading data in these structures is thread safe; i.e. is it safe to recursively read variables from the objects, iterate the ArrayLists etc. to extract information from the structures in multiple threads without synchronization?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
它不安全的唯一原因是,如果一个线程正在写入某个字段,而另一个线程同时从该字段读取数据。如果数据没有变化,则不存在竞争条件。使对象不可变是保证它们线程安全的一种方法。首先阅读 IBM 的这篇文章。
The only reason why it wouldn't be safe is if one thread were writing to a field while another thread was simultaneously reading from it. No race condition exists if the data is not changing. Making objects immutable is one way of guaranteeing that they are thread safe. Start by reading this article from IBM.
ArrayList
的成员不受任何内存屏障的保护,因此不能保证对它们的更改在线程之间可见。即使对列表进行的唯一“更改”是其结构,这也适用。线程之间共享的任何数据都需要“内存屏障”来确保其可见性。有几种方法可以实现这一点。
首先,在构造函数中声明并初始化的任何成员在构造函数完成后对任何线程都是可见的。
对任何声明为 volatile 的成员的更改对所有线程都是可见的。实际上,写入会从任何缓存“刷新”到主内存,任何访问主内存的线程都可以看到它。
现在变得有点棘手了。线程在写入易失性变量之前进行的任何写入也会被刷新。同样,当线程读取易失性变量时,其缓存将被清除,后续读取可能会从主内存中重新填充它。
最后,同步块就像易失性读取和写入,并具有额外的原子性质量。当获取监视器时,线程的读缓存被清除。当监视器被释放时,所有写入都会刷新到主内存。
完成这项工作的一种方法是让填充共享数据结构的线程将结果分配给
易失性
变量(或AtomicReference
,或其他合适的java.util.concurrent
对象)。当其他线程访问该变量时,它们不仅可以保证获得该变量的最新值,还可以保证线程在将值分配给该变量之前对数据结构所做的任何更改。The members of an
ArrayList
aren't protected by any memory barriers, so there is no guarantee that changes to them are visible between threads. This applies even when the only "change" that is ever made to the list is its construction.Any data that is shared between thread needs a "memory barrier" to ensure its visibility. There are several ways to accomplish this.
First, any member that is declared
final
and initialized in a constructor is visible to any thread after the constructor completes.Changes to any member that is declared
volatile
are visible to all threads. In effect, the write is "flushed" from any cache to main memory, where it can be seen by any thread that accesses main memory.Now it gets a bit trickier. Any writes made by a thread before that thread writes to a volatile variable are also flushed. Likewise, when a thread reads a volatile variable, its cache is cleared, and subsequent reads may repopulate it from main memory.
Finally, a
synchronized
block is like a volatile read and write, with the added quality of atomicity. When the monitor is acquired, the thread's read cache is cleared. When the monitor is released, all writes are flushed to main memory.One way to make this work is to have the thread that is populating your shared data structure assign the result to a
volatile
variable (or anAtomicReference
, or other suitablejava.util.concurrent
object). When other threads access that variable, not only are they guaranteed to get the most recent value for that variable, but also any changes made to the data structure by the thread before it assigned the value to the variable.如果数据在创建后从未被修改,那么你应该没问题并且读取将是线程安全的。
为了安全起见,您可以将所有数据成员设为“最终”,并尽可能使所有访问函数可重入;这可以确保线程安全,并且如果您将来更改代码,可以帮助保持代码线程安全。
一般来说,让尽可能多的成员成为“最终”成员有助于减少错误的引入,因此许多人提倡将此作为 Java 最佳实践。
If the data is never modified after it's created, then you should be fine and reads will be thread safe.
To be on the safe side, you could make all of the data members "final" and make all of the accessing functions reentrant where possible; this ensures thread safety and can help keep your code thread safe if you change it in the future.
In general, making as many members "final" as possible helps reduce the introduction of bugs, so many people advocate this as a Java best practice.
就像其他人的答案的附录一样:如果您确定需要同步数组列表,您可以调用 Collections.synchronizedList(myList) ,它将返回一个线程安全的实现。
Just as an addendum to everyone else's answers: if you're sure you need to synchronize your array lists, you can call Collections.synchronizedList(myList) which will return you a thread safe implementation.
我看不出使用多个线程从 ArrayList、字符串和原始值读取会出现什么问题。
只要您只是阅读,就不需要同步。对于字符串和基元来说,这当然是安全的,因为它们是不可变的。对于 ArrayLists 来说它应该是安全的,但我没有授权。
I cannot see how reading from ArrayLists, Strings and primitive values using multiple threads should be any problem.
As long as you are only reading, no synchronization should be necessary. For Strings and primitives it is certainly safe as they are immutable. For ArrayLists it should be safe, but I do not have it on authority.
不要不要使用
java.util.Vector
,如果它们确实不可修改,请使用java.util.Collections.unmodifyingXXX()
包装器,这将保证他们不会改变,并将执行该合同。如果要修改它们,请使用 java.util.Collections.syncronizedXXX()。但这只能保证内部线程安全。将变量设置为final
还将帮助编译器/JIT 进行优化。Do NOT use
java.util.Vector
, usejava.util.Collections.unmodifiableXXX()
wrapper if they truly are unmodifiable, this will guarantee they won't change, and will enforce that contract. If they are going to be modified, then usejava.util.Collections.syncronizedXXX()
. But that only guarantees internal thread safety. Making the variablesfinal
will also help the compiler/JIT with optimizations.