问题
I am learning Java multithreading. I wrote a small piece of code and results in some output which i am not able to understand..please help with some explanation. Posting the code below.
package com.java.learn;
import java.util.ArrayList;
import java.util.List;
public class ListTestWithMultiThread {
static final List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
list.add(Integer.valueOf(i));
}
System.out.println("List size at thread 0 : " + list.size());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 101; i <= 200; i++) {
list.add(Integer.valueOf(i));
}
System.out.println("List size at thread 1 : " + list.size());
}
}).start();
}
}
Some of the o/p in various runs: List size at thread 0 : 134 List size at thread 1 : 200
Exception in thread "Thread-1" List size at thread 0 : 101
java.lang.ArrayIndexOutOfBoundsException: 17
at java.util.ArrayList.add(Unknown Source)
at com.java.learn.ListTestWithMultiThread$2.run(ListTestWithMultiThread.java:25)
at java.lang.Thread.run(Unknown Source)
List size at thread 0 : 106
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 58
at java.util.ArrayList.add(Unknown Source)
at com.java.learn.ListTestWithMultiThread$2.run(ListTestWithMultiThread.java:25)
at java.lang.Thread.run(Unknown Source)
回答1:
You are accessing an data-structure (the list
) that is not designed for concurrent access in parallel without protecting it (e.g. via synchronized
). This will eventually corrupt the internals of the data-structure leading to weird behavior like the exception you get.
Here are two ways how to procted it:
- Use a concurrent data-structure:
List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
Use
synchronized
to protect the list:synchronized(list) { list.add(Integer.valueOf(i)); }
Edit: Since you asked for it, here is how the ArrayList
can get corrupted.
An ArrayList
is backed by an array that has to be resized when the list grows. Resizing here means an new, larger array is allocated and the content of the old one copied to the new one. Here are parts of the code that does that:
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
...
elementData = Arrays.copyOf(elementData, newCapacity);
Now imagine the following: Thread A start resizing the array, it computes the new capacity and starts copying the data in line 4. But before it can copy the reference of the new array to elementData
it gets stoped for whatever reason (this happens all the time). Now Thread B starts resizing the array and finishes. Then it inserts more values into the list and resizes the array again and finishes. Thread B now assumes that the list is large enough for a new value, but before it inserts the value, thread A wakes up and overrides elementData
with the reference to the smaller array it has created. Thread B now tries to insert a value into the smaller array and gets an ArrayIndexOutOfBoundsException
. This all is kinda unlikely but it can happen as you see.
回答2:
You are accessing the list
variable from two different threads without any synchronization (locking). This will result undefined behavior.
You might want to try replacing the list declaration with the following:
static final List list = Collections.synchronizedList(new ArrayList());
Another option is to use Vector
instead of ArrayList
. Vector
is a synchronized collection implementing the List
interface.
回答3:
ArrayList
is not thread safe.ArrayList
implementation is backed up by an Array. There is size variable associatedArrayList
. Whenever we add any element toArrayList
it first ensures it capacity and then adds elements to the array. What state of array list is visible to each Thread is undefined as you are sharing non thread safe implementation between two threads.If you start any thread first, do not assume that it will finish its task first. There is no predicted behavior.
回答4:
I think error is outofbound and not java.util.ConcurrentModificationException To understand the output you should first understand working of arraylist internally. when you add arraylist what happens exactly internally. How the size of ArrayList grows dynamically?
Inside the add(Object) , you will find the following code
public boolean add(E e)
{
ensureCapacity(size+1);
elementData[size++] = e;
return true;
}
Important point to note from above code is that we are checking the capacity of the ArrayList , before adding the element. ensureCapacity() determines what is the current size of occupied elements and what is the maximum size of the array. If size of the filled elements (including the new element to be added to the ArrayList class) is greater than the maximum size of the array then increase the size of array. But the size of the array can not be increased dynamically. So what happens internally is new Array is created with capacity.
So here in your case your Thread 1 and Thread 2 tried and succeed to add elements simultaneously without synchronization error fortunately. Until the Arraylist is full now both threads create 2 different new array. One of thread wins to change reference to its newly created array suppose its thread 1. Now thread 2 tries to copy all elements of newly referenced array unfortunately he is unaware about size change of array thus recalculate size of array and try to add new element at top of the array which does not exist.Thus throws exception out of bound.
Please correct me if I'm understanding wrong.
来源:https://stackoverflow.com/questions/35519352/java-multithreading-behaviour-explanation