【并发编程】ArrayList 非线程安全案例并提供三种解决方案
1. 复现问题
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 复现问题
*
* @author CL
*
*/
public class RecurrenceProblem {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
// 启动30个线程
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
运行结果:
[42251c59]
[42251c59, 5839198b]
[42251c59, 5839198b, 1283d17b]
[42251c59, 5839198b, 1283d17b, 01fce852]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed, a8aa4fea]
Exception in thread "21" [42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed, a8aa4fea, 0d3b8cf8]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed, a8aa4fea, 0d3b8cf8, 6fb56487]
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed, a8aa4fea, 0d3b8cf8, 6fb56487, b9fb25b8]
java.util.ConcurrentModificationException
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed, a8aa4fea, 0d3b8cf8, 6fb56487, b9fb25b8, 3b98b513]
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.c3stones.demo.RecurrenceProblem.lambda$0(RecurrenceProblem.java:22)
at java.lang.Thread.run(Thread.java:745)
[42251c59, 5839198b, 1283d17b, 01fce852, 857eb6d1, eb874846, 7cfa654c, 794c24d5, 68a415a7, f51c911a, 9f99c03a, 84213b11, b1d1c583, 04ecf6c3, 4d16693d, 65a4715e, e968aa23, b60db864, fb3a9828, 3fd4f004, 333308f6, 7143a717, 9ba40ac6, 06ae20ed, a8aa4fea, 0d3b8cf8, 6fb56487, b9fb25b8, 3b98b513, 83bd3f54]
出现:java.util.ConcurrentModificationException
异常
2. 原因剖析
java.util.ConcurrentModificationException
即并发修改异常。
查看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;
}
发现并没有对该方法加锁,因此在并发修改时,必然是线程不安全的,则抛出java.util.ConcurrentModificationException
异常。
3. 解决方案
- Vector
使用Vector代替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;
}
可以看到add方法中使用synchronized实现同步。代码修改为:
import java.util.List;
import java.util.UUID;
import java.util.Vector;
/**
* 解决方案01-Vector
*
* @author CL
*
*/
public class Solution01 {
public static void main(String[] args) {
List<String> list = new Vector<String>();
// 启动30个线程
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
运行结果:
[759f9961]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda]
[759f9961, ff871e85, 4a3939a2, 59e796cf]
[759f9961, ff871e85, 4a3939a2]
[759f9961, ff871e85]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf, 12155931]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf, 12155931, 12e61627]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf, 12155931, 12e61627, fa205c82]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf, 12155931, 12e61627, fa205c82, 04726e78]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf, 12155931, 12e61627, fa205c82, 04726e78, 0ee9c3d5]
[759f9961, ff871e85, 4a3939a2, 59e796cf, cf4dfbda, bbc62489, 439941c6, 7a003645, fc224a2b, f52573e5, 1144e6c3, 4de11c35, ba2e2d82, 95d9f85e, d4cfdf23, 90dd544b, 59e75219, a3787684, fb5cf421, 9ca37cea, 8cbe36f6, 62494d83, 846001e0, ec1efeaf, 12155931, 12e61627, fa205c82, 04726e78, 0ee9c3d5, 097c1d4e]
从运行结果可以看出,已经解决了问题。但是查看Vector类说明和ArrayList类说明:
/*
* @author Lee Boynton
* @author Jonathan Payne
* @see Collection
* @see LinkedList
* @since JDK1.0
*/
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}
/*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see List
* @see LinkedList
* @see Vector
* @since 1.2
*/
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}
可以看出,Vector是JDK1.0时出现的,ArrayList是JDK1.2出现的。因此,官方肯定不建议使用Vector来解决此问题。
- Collections
使用Collections工具类提供的方法来避免ArrayList出现的并发修改问题。代码修改为:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* 解决方案02-Collections
*
* @author CL
*
*/
public class Solution02 {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<String>());
// 启动30个线程
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
Set、Map在创建实例时也可以使用此方式:
Set<String> set = Collections.synchronizedSet(new HashSet<String>());
Map<Integer, String> map = Collections.synchronizedMap(new HashMap<>());
- CopyOnWrite
CopyOnWrite写时复制。CopyOnWriteArrayList.add(...)方法:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可以看出在方法进入时加锁。先创建一个比之前长度大1的数组,并在末尾添加元素,最后再将源容器指向新的容器。这样的好处是源容器不会添加任何元素,这也是一种读写分离的思想。
代码修改为:
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 解决方案03-CopyOnWrite
*
* @author CL
*
*/
public class Solution03 {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<String>();
// 启动30个线程
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
Set、Map在创建实例时也可以使用此方式:
Set<String> set = new CopyOnWriteArraySet<String>();
Map<Integer, String> map = new ConcurrentHashMap<Integer, String>();