Java基础-0x03:ArrayList线程不安全的问题

Morocco

JUC

我们知道ArrayList是线程不安全的

众所周知,ArrayList时线程不安全的,那么有没有什么方法,既保证了
AllrayList的高并发,又保证了线程安全呢?

我编写了一个案例,并给出了解决方案。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 1. 故障现象<br>
* <b>java.util.ConcurrentModificationException</b>
*
* 2. 导致原因<br>
* 、
*
* 3. 解决方案<br>
* <i>3.1 new Vector<>;</i><br>
* <i>3.2 Collections.synchronizedList(new ArrayList)<>();</i><br>
*
* 4. 优化建议<br>
*
* @author allwayz
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
stringList.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(stringList);
},String.valueOf(i)).start();
}
}
}

当多线程执行ArrayList.add()方法时,会报错

1
2
3
4
5
6
7
8
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
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.allwayz.thread.ContainerNotSafeDemo.lambda$main$0(ContainerNotSafeDemo.java:16)
at java.lang.Thread.run(Thread.java:748)

解决方案 一、

1
2
3
List<String> list = new Vector<>();

List<String> list = Collections.synchronizedList(new ArrayList<>());

解决方案 二、

限制不可以使用Vector和Collections工具类

JUC包中有个类叫CopyOnWriteArrayList<E>

为什么CopyOnWriteArrayList可以保证线程安全呢? 来看看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

······

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();

/**
* The array, accessed only via getArray/setArray.
*/
private transient volatile Object[] array;

······

/**
* Appends the specified element to the end of this list.
*
* CopyOnWrite容器即写时复制的容器。往一个容器中添加元素时,不直接往房钱容器object[ ]对象添加,
* 而是先将当前容器object[ ]进行Copy,复制出一个新的容器Object[ ] newElements,然后向新的容
* 器中添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以
* 对CopyOnWrite容器进行并发的读,而不需要加锁,因为当情容器不会添加任何元素。
* 所以CopyOnWrite容器也是一种读写分离的思想。
*
* @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();
}
}

······

}

CopyOnWrite容器即写时复制的容器。往一个容器中添加元素时,不直接往房钱容器object[ ]对象添加,而是先将当前容器object[ ]进行Copy,复制出一个新的容器Object[ ] newElements,然后向新的容器中添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当情容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想。