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, S
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.
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 an AtomicReference
, or other suitable java.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.
Do NOT use java.util.Vector
, use java.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 use java.util.Collections.syncronizedXXX()
. But that only guarantees internal thread safety. Making the variables final
will also help the compiler/JIT with optimizations.
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.
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.
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.