為什么Vector和Collections.SynchronizedList的get方法要加鎖呢?
1. 線程不安全的ArrayList
為什么說ArrayList是線程不安全的:
- add()操作拋出數(shù)組越界異常;
- add()操作會(huì)丟失元素;
- set()操作去修改元素,get()操作去獲取元素時(shí),可以讀到新值也可能讀到舊值,無法保證一致性。
源碼分析:
//存放list集合元素的數(shù)組,默認(rèn)容量10
transient Object[] elementData;
//list大小
private int size;
add()的源碼:
public boolean add(E e) {
//確定添加元素之后,集合的大小是否足夠,若不夠則會(huì)進(jìn)行擴(kuò)容
ensureCapacityInternal(size + 1); // Increments modCount!!
//插入元素
elementData[size++] = e;
return true;
}
場景1:多個(gè)線程都沒進(jìn)行擴(kuò)容,但是執(zhí)行了elementData[size++] = e;時(shí),便會(huì)出現(xiàn)“數(shù)組越界異?!?;
場景2:因?yàn)閟ize++本身就是非原子性的,多個(gè)線程之間訪問沖突,這時(shí)候兩個(gè)線程可能對同一個(gè)位置賦值,就會(huì)出現(xiàn)“size小于期望值的結(jié)果”;
2. Vector和Collections.SynchronizedList的get方法要加鎖呢?
get()操作時(shí)集合中的元素不能并發(fā)的被修改,否則就易出現(xiàn)數(shù)據(jù)問題。
- Vector和Collections.SynchronizedList的get方法加了synchronized后可以保證順序性與實(shí)時(shí)一致性,當(dāng)一個(gè)線程在讀取數(shù)據(jù)時(shí),一定可以看到其他線程解鎖前寫入的全部數(shù)據(jù)。
- 并且Vector和Collections.SynchronizedList的數(shù)組并沒有用volatile修飾,如果不加鎖,也無法保證可見性。
3. 線程安全的3種List集合
//方法上使用sync關(guān)鍵字(讀寫均加鎖)
Vector vector = new Vector();
//寫操作每一次均copy一個(gè)數(shù)組,讀操作不加鎖(寫加鎖性能低,讀不加鎖性能極高)
CopyOnWriteArrayList<Integer> r2 = new CopyOnWriteArrayList<>();
//使用sync代碼塊裝飾傳入List的讀寫操作(讀寫均加鎖)
List<String> r3 = Collections.synchronizedList(new ArrayList<>());
- Vector/Collections.synchronizedList:讀寫均加鎖,來實(shí)現(xiàn)線程安全;
- CopyOnWriteArrayList基于寫時(shí)復(fù)制技術(shù)實(shí)現(xiàn)的,讀操作無鎖(讀取快照),寫操作有鎖,體現(xiàn)了讀寫分離的思想,但是無法提供實(shí)時(shí)一致性。
4. 并發(fā)安全的案例
下面給出一個(gè)案例,即容易出現(xiàn)并發(fā)問題的場景:
public class TestList {
private static final ExecutorService VIEW_EXECUTOR = new ThreadPoolExecutor(2,4,1000,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));
/**
* 目前比較常用的構(gòu)建線程安全的List有三種方法:
* <p>
* 使用Vector容器
* 使用Collections的靜態(tài)方法synchronizedList(List< T> list)
* 采用CopyOnWriteArrayList容器
*/
public static void main(String[] args) {
//常用方式:使用線程池并發(fā)處理,填充結(jié)果
ArrayList<Object> res = new ArrayList<>();
CompletableFuture.runAsync(() -> {
//todo 邏輯處理
//線程不安全,需要使用一個(gè)線程安全的List,這里推薦Collections.synchronizedList
res.add("success");
}, VIEW_EXECUTOR);
}
}
推薦閱讀
重學(xué)Java并發(fā)編程(寫時(shí)復(fù)制技術(shù)在CopyOnWriteArrayList中的應(yīng)用)