一、List
1.1 模擬多線程環境
多線程環境下,會拋出 java.util.ConcurrentModificationException 異常
1 public static void listNotSafe() { 2 List<String> list = new CopyOnWriteArrayList<>(); 3 4 for (int i = 0; i < 30; i++) { 5 new Thread(() -> { 6 list.add(UUID.randomUUID().toString().substring(0, 8)); 7 System.out.println(list); 8 }).start(); 9 } 10 }

1.2 異常原因
多線程環境下,並發爭搶修改導致出現該異常。
1.3 解決辦法
1 // 1. 使用線程安全類 Vector 2 new Vector(); 3 4 // 2. 使用 Collections 工具類封裝 ArrayList 5 Collections.synchronizedList(new ArrayList<>()); 6 7 // 3. 使用 java.util.concurrent.CopyOnWriteArrayList; 8 new CopyOnWriteArrayList<>();
1.4 寫時復制思想
CopyOnWrite 容器即寫時復制的容器。往一個容器添加元素的時候,不直接往當前容器Object[]添加,而是先將當前容器Object[]進行Copy, 復制出一個新的容器Object[] newElements, 然后新的容器Object[] newElements 里添加元素,添加完元素之后,再將原容器的引用指向新的容器 setArray(newElements); 這樣做的好處是可以對CopyOnWrite容器進行並發的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
1 // CopyOnWriteArrayList.java 2 public boolean add(E e) { 3 final ReentrantLock lock = this.lock; 4 lock.lock(); 5 try { 6 Object[] elements = getArray(); 7 int len = elements.length; 8 Object[] newElements = Arrays.copyOf(elements, len + 1); 9 newElements[len] = e; 10 setArray(newElements); 11 return true; 12 } finally { 13 lock.unlock(); 14 } 15 }
二、Set
2.1 線程安全問題
與 List 接口的測試方法相似,同樣會拋出 java.util.ConcurrentModificationException 異常。
2.2 解決辦法
1 // 1. 使用 Collections 工具類封裝 2 Collections.synchronizedSet(new HashSet<>()); 3 4 // 2. 使用 java.util.concurrent.CopyOnWriteArraySet; 5 new CopyOnWriteArraySet<>();
2.3 CopyOnWriteArraySet
final ReentrantLock lock = this.lock; 為什么聲明為 final?參考可以看看這個 https://blog.csdn.net/zqz_zqz/article/details/79438502
1 // 底層實際上是一個 CopyOnWriteArrayList 2 public class CopyOnWriteArraySet<E> extends AbstractSet<E> 3 implements java.io.Serializable { 4 private static final long serialVersionUID = 5457747651344034263L; 5 6 private final CopyOnWriteArrayList<E> al; 7 8 // ... 9 }
1 // 添加元素,相當於調用 CopyOnWriteArrayList 的 addIfAbsent() 方法 2 public class CopyOnWriteArraySet<E> { 3 public boolean add(E e) { 4 return al.addIfAbsent(e); 5 } 6 } 7 8 /** 9 * CopyOnWriteArrayList 的 addIfAbsent() 方法 10 * Set 集合中的元素不可重復,如果原集合中有要添加的元素,則直接返回 false 11 * 否則,將該元素加入集合中 12 */ 13 public boolean addIfAbsent(E e) { 14 Object[] snapshot = getArray(); 15 return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : 16 addIfAbsent(e, snapshot); 17 } 18 19 /** 20 * 重載的 addIfAbsent() 方法,用於真正添加元素加鎖后,再次獲取集合,與剛才拿到的集合比較, 21 * 兩次拿到的不一樣,說明集合被其他線程修改過了,重新比較最新集合中有沒有該元素,如果比較 22 * 后,沒有返回 false,說明沒有該元素,執行下面的添加方法。 23 */ 24 private boolean addIfAbsent(E e, Object[] snapshot) { 25 final ReentrantLock lock = this.lock; 26 lock.lock(); 27 try { 28 Object[] current = getArray(); 29 int len = current.length; 30 if (snapshot != current) { 31 // Optimize for lost race to another addXXX operation 32 int common = Math.min(snapshot.length, len); 33 for (int i = 0; i < common; i++) 34 if (current[i] != snapshot[i] && eq(e, current[i])) 35 return false; 36 if (indexOf(e, current, common, len) >= 0) 37 return false; 38 } 39 Object[] newElements = Arrays.copyOf(current, len + 1); 40 newElements[len] = e; 41 setArray(newElements); 42 return true; 43 } finally { 44 lock.unlock(); 45 } 46 }
三、Map
3.1 線程安全問題
和上面一樣,多線程環境下,會拋出 java.util.ConcurrentModificationException 異常。
3.2 解決辦法
1 // 使用 Collections 工具類 2 Collections.synchronizedMap(new HashMap<>()); 3 4 // 使用 ConcurrentHashMap 5 new ConcurrentHashMap<>();
3.3 HashMap、Hashtable 和 ConcurrentHashMap 的區別
繼承不同:HashMap繼承AbstractMap, Hashtable繼承Dictonary,ConcurrentHashMap除了繼承AbstractMap還實現了ConcurrentMap接口
線程是否安全:HashMap非線程安全,ConcurrentHashMap 和 Hashtable 線程安全,但是他們的實現機制不同,Hashtabl e使用synchronized實現同步方法,而ConcurrentHashMap降低鎖的粒度,擁有更好的並發性能。
Key-Value值:ConcurrentHashMap和Hashtable都不允許value和key為null,但是HashMap允許唯一的key為null,和任意個value為null
哈希算法不同:HashMap 和 Jdk 8 中的 ConcurrentHashMap 的算法一致都是使用 key 的 hashcode 值進行高16位和低16位異或再取模長度,而Hashtable是直接對 key 的hashcode值進行取模操作 。
擴容機制不同:ConcurrentHashMap和HashMap的擴容機制和初始容量一致,擴容為原有數組長度的兩倍,初始容量為16,但是hashtable中的初始容量為11,容量為原有長度的兩倍+1。
失敗機制:ConcurrentHashMap支持安全失敗,HashMap和hashtable支持的快速失敗
查詢方法:HashMap沒有contains方法,但是擁有containsKey和containsValue方法,Hashtable和ConcurrentHashMap還支持contains方法
迭代方式:ConcurrentHashMap和Hashtable還支持Enumeration迭代方式