【JUC并发编程05】集合的线程安全
文章目錄
- 5 集合的線程安全
- 5.1 集合線程不安全演示
- 5.2 解決方案-Vector
- 5.3 解決方案-Collections
- 5.4 解決方案-CopyOnWriteArrayList
- 5.5 對(duì)上述三種方式的總結(jié)
- 5.6 HashSet的線程不安全
- 5.7 HashMap的線程不安全
5 集合的線程安全
5.1 集合線程不安全演示
在學(xué)習(xí)集合線程安全之前,先來(lái)看一下為什么在多線程中會(huì)出現(xiàn)不安全。
以 ArrayList 為例,我們進(jìn)入 ArrayList 源碼,找到 add() 方法,源碼如下
public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true; }顯然,add() 方法沒(méi)有使用同步互斥,所以在多線程并發(fā)是,會(huì)出現(xiàn)線程異常,測(cè)試代碼如下
public class SetUnsefertyTest {public static void main(String[] args) {// 創(chuàng)建ArrayList 集合ArrayList<String> list = new ArrayList<>();// 創(chuàng)建10個(gè)線程,往 list 中添加元素for (int i = 0; i < 10; i++) {new Thread(()->{// 向集合中添加內(nèi)容list.add(UUID.randomUUID().toString().substring(0,8));// 從集合中取出內(nèi)容System.out.println(list);},String.valueOf(i)).start();}} }會(huì)出現(xiàn)如下異常
解決該方法主要有三種,即使用這三個(gè)類:Vector、Collections、CopyOnWriteArrayList(常用)
5.2 解決方案-Vector
進(jìn)入在Vector的底層實(shí)現(xiàn),找到 add() 方法是線程安全的,源代碼如下,可以發(fā)現(xiàn) Vector 將 add() 加上同步關(guān)鍵字了
public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true; }但是 Vector 用的不多,因?yàn)槊看螌?duì)添加的元素上鎖,而且使用的是重量級(jí)鎖synchronized是十分占用資源的,效率是十分低下的。其用法和 ArrayList 一樣。
5.3 解決方案-Collections
進(jìn)入 Collections 的底層,找到 synchronizedList(List list) 方法,源代碼如下,synchronizedList(List list) 方法返回指定列表支持的同步(線程安全的)列表
public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list) :new SynchronizedList<>(list)); }static <T> List<T> synchronizedList(List<T> list, Object mutex) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list, mutex) :new SynchronizedList<>(list, mutex)); }對(duì) Collections 的使用如下
List<String> list = Collections.synchronizedList(new ArrayList<>());5.4 解決方案-CopyOnWriteArrayList
這種方法用的最多。
CopyOnWriteArrayList涉及的底層原理為 寫(xiě)時(shí)復(fù)制技術(shù)
- 讀的時(shí)候并發(fā)(多個(gè)線程操作)
- 寫(xiě)的時(shí)候獨(dú)立,先復(fù)制相同的空間到某個(gè)區(qū)域,將其寫(xiě)到新區(qū)域,舊新合并,并且讀新區(qū)域(每次加新內(nèi)容都寫(xiě)到新區(qū)域,覆蓋合并之前舊區(qū)域,讀取新區(qū)域添加的內(nèi)容)
進(jìn)入 CopyOnWriteArrayList 底層,來(lái)看一下它是怎么實(shí)現(xiàn)的,其 add() 源代碼如下
public boolean add(E e) {// 聲明一個(gè)重進(jìn)入鎖final ReentrantLock lock = this.lock;// 上鎖lock.lock();try {// 獲取原來(lái)的列表Object[] elements = getArray();// 獲取原來(lái)列表的長(zhǎng)度int len = elements.length;// 復(fù)制一個(gè)與原來(lái)的列表一樣的列表Object[] newElements = Arrays.copyOf(elements, len + 1);// 將新加入的元素放到列表末尾newElements[len] = e;// 舊新合并setArray(newElements);return true;} finally {// 解鎖lock.unlock();} }對(duì) CopyOnWriteArrayList 的使用如下
List<String> list = new CopyOnWriteArrayList<>();5.5 對(duì)上述三種方式的總結(jié)
對(duì)比三者來(lái)看,Vector和Collections雖然也可以實(shí)現(xiàn)同步,但由于這兩種方法在底層都使用了synchronized重量級(jí)鎖,使其效率很低,所以對(duì) ArrayList 的同步主要采用 CopyOnWriteArrayList
5.6 HashSet的線程不安全
HashSet 同時(shí)讀寫(xiě)時(shí)也會(huì)出現(xiàn) ConcurrentModificationException 異常
進(jìn)入 HashSet 底層,來(lái)看一下它是怎么實(shí)現(xiàn)的,其 add() 源代碼如下
public boolean add(E e) {return map.put(e, PRESENT)==null; }顯然,他的問(wèn)題和 ArrayList 一樣,沒(méi)有對(duì) add(E e) 方法做同步處理
其解決方法與 CopyOnWriteArrayList 類似,在 JDK1.8 中,也有一個(gè)類叫做 CopyOnWriteArraySet ,其底層代碼如下
public boolean add(E e) {// private final CopyOnWriteArrayList<E> al;return al.addIfAbsent(e); }通過(guò) Debug 找到了對(duì)關(guān)鍵的一個(gè)函數(shù),發(fā)現(xiàn)其實(shí)現(xiàn)方式與 CopyOnWriteArrayList 底層實(shí)現(xiàn)方式類似
// e 表示添加的元素 // snapshot 表示被復(fù)制的列表 private boolean addIfAbsent(E e, Object[] snapshot) {final ReentrantLock lock = this.lock;lock.lock();try {// 獲取當(dāng)前列表和列表長(zhǎng)度Object[] current = getArray();int len = current.length;// 如果現(xiàn)在的列表和之前的列表不同,其實(shí)顯然是不同的if (snapshot != current) {// Optimize for lost race to another addXXX operation// 取較小的長(zhǎng)度列表int common = Math.min(snapshot.length, len);for (int i = 0; i < common; i++)if (current[i] != snapshot[i] && eq(e, current[i]))return false;if (indexOf(e, current, common, len) >= 0)return false;}// 后面就是將其寫(xiě)到新區(qū)域,舊新合并Object[] newElements = Arrays.copyOf(current, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();} }對(duì) CopyOnWriteArraySet 的使用如下
CopyOnWriteArraySet<String> list = new CopyOnWriteArraySet<>();5.7 HashMap的線程不安全
HashMap 同時(shí)讀寫(xiě)時(shí)一樣會(huì)出現(xiàn) ConcurrentModificationException 異常
進(jìn)入 HashMap 底層,來(lái)看一下它是怎么實(shí)現(xiàn)的,其 put(K key, V value) 源代碼如下
public V put(K key, V value) {return putVal(hash(key), key, value, false, true); }在 JDK1.8 中,也有一個(gè)類叫做 ConcurrentHashMap ,實(shí)現(xiàn) HashMap 的同步問(wèn)題,其底層代碼如下
final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;// 若為空,就初始化if (tab == null || (n = tab.length) == 0)tab = initTable();// 計(jì)算hash值,得到下標(biāo)else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 傳入?yún)?shù)if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break; // no lock when adding to empty bin}// 當(dāng)有兩個(gè)及以上的線程正在擴(kuò)容else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {V oldVal = null;// 在這里加上了鎖synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null; }對(duì) ConcurrentHashMap 的使用如下
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();總結(jié)
以上是生活随笔為你收集整理的【JUC并发编程05】集合的线程安全的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【JUC并发编程04】线程间定制化通信(
- 下一篇: 【JUC并发编程06】多线程锁 (公平锁