集合執行緒安全問題
JDK Version:9
首先說下集合執行緒安全是什麼:當多個執行緒對同一個集合進行新增和查詢的時候,出現異常錯誤。
復現例子:
package com.JUC;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ListSecutity04 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
效果圖:
可以看到報ConcurrentModificationException
併發修改異常;出現該錯誤的問題是,在ArrayList中的add方法沒有加鎖。
檢視其原始碼:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
//----------------------------------------
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
//----------------------------------------
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
解決方式-:Vector和Conllections
見名知意,標題的兩種方法都是比較古老的方法,使用Vector是因為其add方法中加的sychronized關鍵字修飾的
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
另一種方法是在Collection中的工具類synchronizedCollection(Collection<T> c)
返回指定 collection 支援的同步(執行緒安全的)collection。
反覆執行測試,程式碼通過:
package com.JUC;
import java.util.*;
public class ListSecutity04 {
public static void main(String[] args) {
// List<String> list = new ArrayList<>();
// List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList());
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
雖然但是,我們選擇CopyOnWriteArrayList
;
解決方式二:CopyOnWriteArrayList
寫時複製技術:
List<String> list = new CopyOnWriteArrayList<>();
其思想:
在多執行緒的情況下,當對集合進行寫的操作的時候,系統先將原來的內容(A)複製一份為(B),原來的內容(A)可以進行併發讀,複製後的內容(B)寫入新的內容,當內容寫入完成後,A與B實現覆蓋或者說合並。
其中陣列的定義我們使用的是volatile定義的,這樣,每個執行緒可以實時的觀察到陣列的變化。
private transient volatile Object[] array;
final void setArray(Object[] a) {
array = a;
}
add()方法原始碼:對lock物件加了鎖
final transient Object lock = new Object();
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray(); //獲取原內容
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); //陣列複製
newElements[len] = e; //新增新的內容
setArray(newElements); //覆蓋原陣列
return true;
}
}
CopyOnWriteArrayList最大特點,讀寫分離,最終一致。比synchronized悲觀鎖效能較好。缺點就是,複製需要佔用記憶體,可能出現OOM的情況。
與之類似執行緒不安全的集合有:HashMap,HashSet,其解決方法類似,在JUC中都有對應,
我們也可以進行程式碼的編寫,並進入原始碼簡單分析一下:
HashSet--CopyOnWriteArraySet
首先檢視HashSet中的add方法的原始碼:(執行緒不安全)
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//---------map中
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
從中可以看到,set集合底層使用的是map集合中的put方法,進入其方法中檢視,是沒有同步相關的程式碼修飾的。
也可以看出來Set集合是無序且不重複的,set中傳入的E最後被傳入到map中作為key。
然後檢視下CopyOnWriteArraySet中add的原始碼:(執行緒安全)
public boolean add(E e) {
return al.addIfAbsent(e); //CopyOnWriteArrayList<E> al = new CopyOnWriteArrayList<E>()
}
//-------------addIfAbsent---------------
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
//---------------------addIfAbsent------------------------
private boolean addIfAbsent(E e, Object[] snapshot) {
synchronized (lock) {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i]
&& Objects.equals(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
從原始碼可以看到,set使用的是CopyOnWriteArrayList,最終新增的資料在addIfAbsent方法中進行了同步。
HashMap--ConcurrentHashMap
HashMap解決的方式就是ConcurrentHashMap,其原始碼中採用的也是synchronized修飾的,具體的新增的流程,程式碼沒讀懂,暫且不表。
歡迎朋友分享下該部分的優質部落格。