What are the possible problems caused by adding elements to unsynchronized ArrayList's object by multiple threads simultaneously?
Tried to run some experiments with a static ArrayList with multiple threads but couldn't find much.
Here i am expecting much of the side effects of not synchronizing an ArrayList or like Objects in a multithreaded environment.
Any good example showing side effects would be appreciable. thanks.
below is my little experiment which ran smoothly without any exception.
I also wonder why it didn't throw any ConcurrentModificationException
?
import java.util.ArrayList;
import java.util.List;
public class Experiment {
static List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("A " + i);
new Thread(new Worker(list, "" + i)).start();
}
}
}
class Worker implements Runnable {
List<Integer> al;
String name;
public Worker(List<Integer> list, String name) {
this.al = list;
this.name = name;
}
@Override
public void run() {
while (true) {
int no = (int) (Math.random() * 10);
System.out.println("[thread " + name + "]Adding:" + no + "to Object id:" + System.identityHashCode(al));
al.add(no);
}
}
}
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. Structural modification implies the addition or deletion of element(s) from the list or explicitly resizes the backing array.
If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. Since there is no synchronization internally, what you theorize is not plausible.
Any method that touches the Vector 's contents is thread safe. ArrayList , on the other hand, is unsynchronized, making them, therefore, not thread safe. With that difference in mind, using synchronization will incur a performance hit. So if you don't need a thread-safe collection, use the ArrayList .
The collection interfaces in Java – List , Set , Map – have basic implementations that are not threadsafe. The implementations of these that you've been used to using, namely ArrayList , HashMap , and HashSet , cannot be used safely from more than one thread.
By adding element into unsunchronized ArrayList used by multiple thread, you may get null value in place of actual value as you desired.
This happen because of following code of ArrayList class.
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList class first check its current capacity and if require then increment its capacity(default capacity is 10 and next increment (10*3)/2) and put default class level value in new space.
Let suppose we are using two thread and both comes at same time to add one element and found that default capacity(10) has been fulled and its time to increment its capacity.At First threadOne comes and increment the size of ArrayList with default value using ensureCapacity method(10+(10*3/2)) and put its element at next index(size=10+1=11) and now new size is 11. Now second thread comes and increment the size of same ArrayList with default value using ensureCapacity method(10+(10*3/2)) again and put its element at next index (size=11+1=12) and now new size is 12. In this case you will get null at index 10 which is the default value.
Here is the same code for above.
package com;
import java.util.ArrayList;
import java.util.List;
public class Test implements Runnable {
static List<Integer> ls = new ArrayList<Integer>();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Test());
Thread t2 = new Thread(new Test());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(ls.size());
for (int i = 0; i < ls.size(); ++i) {
System.out.println(i + " " + ls.get(i));
}
}
@Override
public void run() {
try {
for (int i = 0; i < 20; ++i) {
ls.add(i);
Thread.sleep(2);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
output:
39
0 0
1 0
2 1
3 1
4 2
5 2
6 3
7 3
8 4
9 4
10 null
11 5
12 6
13 6
14 7
15 7
16 8
17 9
18 9
19 10
20 10
21 11
22 11
23 12
24 12
25 13
26 13
27 14
28 14
29 15
30 15
31 16
32 16
33 17
34 17
35 18
36 18
37 19
38 19
After running two or three time you will get null value sometime at index 10 and some time at 16.
As mentioned in above Answer by noscreenname you may get ArrayIndexOutOfBoundsException from this code code. If you remove Thread.sleep(2) it will generate frequently.
Please check total size of array which is less then as you required. According to code it should 40(20*2) but you will get different on every time.
Note : It is possible you may have to run this code multiple time to generate one or multiple scenario.
You will usually encounter issues when the list is resized to accommodate more elements. Look at the implementation of ArrayList.add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
if there is no synchronization, the array's size will change between the call to ensureCapacityInternal
and the actual element insertion. This will eventually cause an ArrayIndexOutOfBoundsException
to be thrown.
Here is the code that produces this behavior
final ExecutorService exec = Executors.newFixedThreadPool(8);
final List<Integer> list = new ArrayList<>();
for (int i = 0; i < 8; i++) {
exec.execute(() -> {
Random r = new Random();
while (true) {
list.add(r.nextInt());
}
});
}
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