集合類安全問題
集合類在寫項目的過程中使用的十分頻繁,但其實常用的幾種集合類並不是線程安全的,在多線程的場景下使用可能會出現問題,用一個小case來模擬一下,本文采用ArrayList爲例,Map和Set原理類似。
Eg: 新建個字符串集合,遍歷,一邊往集合裏寫數據,一邊打印集合內數據。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "A").start();
}
}
運行會報出如下錯誤:java.util.ConcurrentModificationException
java.util.ConcurrentModificationException
at java.util.ArrayListItr.next(ArrayList.java:857)
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 app.noSafeDemo.lambda$0(noSafeDemo.java:20)
at java.lang.Thread.run(Thread.java:748)
解決方法
- 使用Vector
使用線程安全的Vector,代碼進行稍微修改。
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 15; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "A").start();
}
}
Vector爲什麼可以做到線程安全?通過Jdk源碼查看,可以看出,Vector的add方法使用了synchronized關鍵字修飾。
- Collections工具類
Collections工具類是Jdk提供的工具類,包含了多種多樣的接口幫助我們將集合包裝成線程安全的。
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 15; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "A").start();
}
}
- CopyOnWriteArrayList
CopyOnWriteArrayList是JUC中提供的接口,它的底層採用了寫時複製,讀寫分離的思想,在往集合容器中添加元素時,不會直接加入集合中的Object[],而是先將Object[]複製一份成Object[] newElements,向Object[] newElements添加,添加完成後將引用指向Object[] newElements。
我們通過源碼來具體看看是怎麼執行的。
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 15; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "A").start();
}
}