2019独角兽企业重金招聘Python工程师标准>>>
之前一直听说ArrayList线程不安全的说法,百度了一下不安全的分析和列子,感觉都不太满意,最后边实在不行只好自己分析了。
1、多线程插入测试
代码实例ArrayListTestDemo1.java:
package com.hjr.back16.common.util;
import static java.lang.System.*;
import java.util.ArrayList;
import java.util.List;
/**
* 测试ArrayList线程不安全的例子
* @author scuechjr
* @date 2016-4-24 1:29:48
*/
public class ArrayListTestDemo1 {
public static Counter counter = new Counter();
public static void main(String[] args) {
final List list = new ArrayList();
// for (int i = 0; i < 10000; i++) {
// list.add(i);
// }
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++) {
list.add(1000*j + j); // 结果中抛出异常的代码
counter.increment();
}
}
}.start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println("list size: " + list.size());
out.println("Counter: " + counter.getValue());
}
}
class Counter {
private int value;
public synchronized int getValue() {
return value;
}
public synchronized int increment() {
return ++value;
}
public synchronized int decrement() {
return --value;
}
}
Demo执行结果:
Exception in thread "Thread-5" Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49
at java.util.ArrayList.add(ArrayList.java:441)
at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 51
at java.util.ArrayList.add(ArrayList.java:441)
at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 54
at java.util.ArrayList.add(ArrayList.java:441)
at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 50
at java.util.ArrayList.add(ArrayList.java:441)
at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 52
at java.util.ArrayList.add(ArrayList.java:441)
at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
list size: 4858
Counter: 5050
分析执行结果的时候,我们可能会发现两个问题:1)为什么抛出异常;2)为什么list size为什么比Counter小?
1)为什么抛出异常
我们先看下ArrayList.add()方法的源码:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
上边源码中,ensureCapacityInternal方法主要是判断需要的容量(size+1)是否大于
ArrayList的容量(elementData.length),如果大于
根据规则增加ArrayList的容量,否则
直接返回。
由于ArrayList.add()没有同步锁,所以多线程调用这个方法的时候,有可能多个线程拿到相同的size值去与ArrayList的容量做比较,而执行到elemntData[size++] = e;时却是有序的,这时,由于ensureCapacityInternal()没有适当的扩大ArrayList的容量,从而导致插入数据的长度大于ArrayList的剩余容量,于是也就抛出了越界的异常(java.lang.ArrayIndexOutOfBoundsException),图解:
2)为什么list size为什么比Counter小
对于这个问题,需要说明的是如代码所示,Counter.increment()方法是加了同步锁的,所以Counter输出的值就是list.add()成功的次数。然而,list size比Counter小,同样是因为多线程进入add方法时,拿到相同的size执行elementData[size++] = e;这句代码,而Java的自增操作又不是原子性操作,这样就导致了多个线程拿相同的size执行加1的操作,最后边就出现了list size小于Counter,也就小于世界成功执行add()方法次数的问题。
2、迭代器遍历List时做修改
代码实例ArrayListTestDemo2.java:
package com.hjr.back16.common.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 测试ArrayList线程不安全的例子
* @author scuechjr
* @date 2016-4-24 1:29:48
*/
public class ArrayListTestDemo2 {
public static void main(String[] args) {
final List list = new ArrayList();
for (int i = 0; i < 10000; i++) {
list.add(i);
}
// 列表遍历线程
new Thread() {
public void run() {
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next(); // 异常抛出的地方
}
}
}.start();
// 列表新增元素线程
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++) {
list.add(1000*j + j);
}
}
}.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Demo执行结果:
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.hjr.back16.common.util.ArrayListTestDemo2$1.run(ArrayListTestDemo2.java:25)
异常来源代码片段ArrayList内部类Itr:
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
... ...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
根据异常信息,我们知道异常是Itr.checkForComodification()这个方法抛出的
。其中,modCount是一个ArrayList用于记录ArrayList修改(包括列表元素的增删改等)次数的成员变量;expectedModCount是初始话一个新的Iterator时的modCount值。整个方法主要用于判断ArrayList在Iterator遍历的过程中,是否发生修改,如果发生修改(expectedModCount与modCount不同),抛出异常(这其实是Java集合的一种错误机制:fail-fast)。