一、发现并发问题
1.1 测试代码
public class Client {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"C").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list.size() = " + list.size());
}
}
1.2 问题一:ArrayIndexOutOfBoundsException
我们先来看看ArrayList.add()的源码
/**
* 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;
}
在多线程环境中时,多个线程同时进入add()方法,同时检查容量,例如当前容量为5,而已占用4。三个线程同时检查,都发现还有容量,则都同时添加元素。由此导致ArrayIndexOutOfBoundsException。
1.3 问题二:实际插入元素个数小于预期插入元素个数
从运行结果可以看出,最终list.size()只有11680 <= 30000。我们希望能够插入30000个元素,可是实际上只插入了<= 30000个元素。还是从源码入手:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
试想一下,如果多个线程同时向size位插入元素,且都没有来得及size++,那么导致的结果就是多个元素被插入在了同一个位置,相互抵消。
二、解决并发问题
2.1 使用Vector
早期,IT前人为了解决List在并发时出现的问题,引入了Vector实现类。Vetor的实现方式与ArrayList大同小异,它的底层也是一个数组,在添加时自增长。我们先来看看Vector.add()的源码
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
与ArrayList不同的是,它的add()方法带有synchronized关键字。这表明当线程调用该方法时,会自动占用锁,直到这个线程的任务完成,期间不会放弃该锁。而且当线程占有该锁时,别的线程无法进入Vetor类调用带有synchronized关键字的方法。这很好的避免了多线程竞争的现象,从而保证了并发安全
我们现在将ArrayList换成Vetor再试试:
public class Client {
public static void main(String[] args) {
Vector<Integer> list = new Vector<>();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"C").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list.size() = " + list.size()); // 30000
}
}