ArrayList
不是線程安全類,在多線程同時寫的情況下,會拋出java.util.ConcurrentModificationException
異常。如下代碼,開啓30個線程同時對ArrayList進行寫操作,會報併發異常錯。
private static void listNotSafe() {
List<String> list=new ArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + "\t" + list);
}, String.valueOf(i)).start();
}
}
解決方法:
- 使用
Vector
(ArrayList
所有方法加synchronized
,太重)。 - 使用
Collections.synchronizedList()
轉換成線程安全類。 - 使用
java.concurrent.CopyOnWriteArrayList
(推薦)
第一個方案中,vector源碼中可以看到,用上了synchronize修飾
而且他是在1.0就出來了;而ArrayList是1.2出來的,由於vector性能太差,後者是爲了解決這個問題而搞出來的,把併發問題交給其他方案去處理是更妥當的。
第二個方案中,可在jdk API中看到:
其中hashset的底層是hashmap實現的,他的value存儲的都是固定值。
HashSet
底層是用HashMap
實現的。既然是用HashMap
實現的,那HashMap.put()
需要傳兩個參數,而HashSet.add()
只傳一個參數,這是爲什麼?實際上HashSet.add()
就是調用的HashMap.put()
,只不過Value被寫死了,是一個private static final Object
對象。
HashMap
不是線程安全的,Hashtable
是線程安全的,但是跟Vector
類似,太重量級。所以也有類似CopyOnWriteMap,只不過叫ConcurrentHashMap
。
第三個方案中原理:
寫的時候必須對對象加鎖。
寫是在自己空間操作的,不影響併發讀,所以這是讀寫分離的。
List<String> list = new CopyOnWriteArrayList<>();