Under what circumstances would an unsynchronized collection, say an ArrayList, cause a problem? I can't think of any, can someone please give me an example where an ArrayList causes a problem and a Vector solves it? I wrote a program that have 2 threads both modifying an arraylist that has one element. One thread puts "bbb" into the arraylist while the other puts "aaa" into the arraylist. I don't really see an instance where the string is half modified, I am on the right track here?
Also, I remember that I was told that multiple threads are not really running simultaneously, 1 thread is run for sometime and another thread runs after that(on computers with a single CPU). If that was correct, how could two threads ever access the same data at the same time? Maybe thread 1 will be stopped in the middle of modifying something and thread 2 will be started?
Many Thanks in advance.
Implementation of ArrayList is not synchronized by default. It means if a thread modifies it structurally and multiple threads access it concurrently, it must be synchronized externally.
Vectors are synchronized. Any method that touches the Vector 's contents is thread safe. ArrayList , on the other hand, is unsynchronized, making them, therefore, not thread safe.
To synchronize ArrayList, we can use two JDK provided methods. Collections. synchronizedList() method – It returns synchronized list backed by the specified list. CopyOnWriteArrayList class – It is a thread-safe variant of ArrayList.
There is no guaranteed behavior for what happens when add is called concurrently by two threads on ArrayList. However, it has been my experience that both objects have been added fine. Most of the thread safety issues related to lists deal with iteration while adding/removing.
There are three aspects of what might go wrong if you use an ArrayList (for example) without adequate synchronization.
The first scenario is that if two threads happen to update the ArrayList at the same time, then it may get corrupted. For instance, the logic of appending to a list goes something like this:
public void add(T element) { if (!haveSpace(size + 1)) { expand(size + 1); } elements[size] = element; // HERE size++; }
Now suppose that we have one processor / core and two threads executing this code on the same list at the "same time". Suppose that the first thread gets to the point labeled HERE
and is preempted. The second thread comes along, and overwrites the slot in elements
that the first thread just updated with its own element, and then increments size
. When the first thread finally gets control, it updates size
. The end result is that we've added the second thread's element and not the first thread's element, and most likely also added a null
to the list. (This is just illustrative. In reality, the native code compiler may have reordered the code, and so on. But the point is that bad things can happen if updates happen simultaneously.)
The second scenario arises due to the caching of main memory contents in the CPU's cache memory. Suppose that we have two threads, one adding elements to the list and the second one reading the list's size. When on thread adds an element, it will update the list's size
attribute. However, since size
is not volatile
, the new value of size
may not immediately be written out to main memory. Instead, it could sit in the cache until a synchronization point where the Java memory model requires that cached writes get flushed. In the meantime, the second thread could call size()
on the list and get a stale value of size
. In the worst case, the second thread (calling get(int)
for example) might see inconsistent values of size
and the elements
array, resulting in unexpected exceptions. (Note that kind of problem can happen even when there is only one core and no memory caching. The JIT compiler is free to use CPU registers to cache memory contents, and those registers don't get flushed / refreshed with respect to their memory locations when a thread context switch occurs.)
The third scenario arises when you synchronize operations on the ArrayList
; e.g. by wrapping it as a SynchronizedList
.
List list = Collections.synchronizedList(new ArrayList()); // Thread 1 List list2 = ... for (Object element : list2) { list.add(element); } // Thread 2 List list3 = ... for (Object element : list) { list3.add(element); }
If thread2's list is an ArrayList
or LinkedList
and the two threads run simultaneously, thread 2 will fail with a ConcurrentModificationException
. If it is some other (home brew) list, then the results are unpredictable. The problem is that making list
a synchronized list is NOT SUFFICIENT to make it thread-safe with respect to a sequence of list operations performed by different threads. To get that, the application would typically need to synchronize at a higher level / coarser grain.
Also, I remember that I was told that multiple threads are not really running simultaneously, 1 thread is run for sometime and another thread runs after that(on computers with a single CPU).
Correct. If there is only one core available to run the application, obviously only one thread gets to run at a time. This makes some of the hazards impossible and others become much less likely likely to occur. However, it is possible for the OS to switch from one thread to another thread at any point in the code, and at any time.
If that was correct, how could two threads ever access the same data at the same time? Maybe thread 1 will be stopped in the middle of modifying something and thread 2 will be started?
Yup. That's possible. The probability of it happening is very small1 but that just makes this kind of problem more insidious.
1 - This is because thread time-slicing events are extremely infrequent, when measured on the timescale of hardware clock cycles.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With