这里主要是根据源码来对对象进行分析。
1.ArrayList
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
在ArrayList中主要有两个变量,elementData是一个Object数组,size用来保存节点的个数。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这是ArrayList中添加对象的方法,ensureCapacityInternal()主要是用来保证数组存储容量大小的。这里主要可能会导致线程不安全的就是elementData[size++] = e这步操作。看到size++就知道这不是一个原子操作,其主要分为赋值和加1两步。我们可以把该过程分为如下两步:
elementData[size] = e;
size = size + 1;
例子
假设此时ArrayList中没有元素。
- A线程执行了ArrayList中的add方法添加了元素X;
- 在A线程执行到得到size的大小为0时,B线程同样执行了add方法添加了元素Y,并且也得到size的值为0;
- 然后两个线程都成功执行了add方法;
- 我们获取ArrayList中的元素和size值会发现size值为2,而元素只有X或Y中的一个。
因为A、B两线程在添加元素时都获取到size的值,并且都为0,所以两个元素会存放到相同的位置,而size却执行了两次加一操作。
2.LinkedList
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
代码中,modCount记录了LinkedList结构被修改的次数。Iterator初始化时,expectedModCount=modCount。任何通过Iterator修改LinkedList结构的行为都会同时更新expectedModCount和modCount,使这两个值相等。通过LinkedList对象修改其结构的方法只更新modCount。所以假设有两个线程A和B。A通过Iterator遍历并修改LinkedList,而B,与此同时,通过对象修改其结构,那么Iterator的相关方法就会抛出异常。这是相对容易发现的由线程竞争造成的错误。
网友评论