您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何理解Java并發容器J.U.C,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
> J.U.C
是java.util.concurrent
的簡寫,里面提供了很多線程安全的集合。
CopyOnWriteArrayList
介紹> CopyOnWriteArrayList
相比于ArrayList
是線程安全的,字面意思是寫操作時復制
。CopyOnWriteArrayList
使用寫操作時復制
技術,當有新元素需要加入時,先從原數組拷貝一份出來。然后在新數組里面加鎖添加,添加之后,將原來數組的引用指向新數組。
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); //加鎖 try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; //引用指向更改 setArray(newElements); return true; } finally { lock.unlock(); //釋放鎖 } }
> 從上面的源碼中得到CopyOnWriteArrayList
的add
操作是在加鎖的保護下完成的。加鎖是為了多線程對CopyOnWriteArrayList
并發add
時,復制多個副本,把數據搞亂。
public E get(int index) { return get(getArray(), index); }
> 以上代碼顯示get
是沒有加鎖的
> 如果出現并發get
,會有以下3中情況。
如果寫操作未完成,那么直接讀取原數組的數據;
如果寫操作完成,但是引用還未指向新數組,那么也是讀取原數組數據;
如果寫操作完成,并且引用已經指向了新的數組,那么直接從新數組中讀取數據。
CopyOnWriteArrayList
多線程代碼演示。package com.rumenz.task; import java.util.List; import java.util.concurrent.*; //線程安全 public class CopyOnWrireArrayListExample { public static Integer clientTotal=5000; public static Integer threadTotal=200; private static List<integer> list=new CopyOnWriteArrayList(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final Integer j=i; executorService.execute(()->{ try{ semaphore.acquire(); update(j); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size:"+list.size()); } private static void update(Integer j) { list.add(j); } } //size:5000
CopyOnWriteArrayList
使用場景由于在add
的時候需要拷貝原數組,如果原數組內容比較多,比較大,可能會導致young gc
和full gc
。
不能用于實時讀的場景,像拷貝數組,新增元素都需要時間,所以調用get
操作后,有可能得到的數據是舊數據,雖然CopyOnWriteArrayList
能做到最終一致性,但是沒有辦法滿足實時性要求。
CopyOnWriteArrayList
適合讀多寫少的場景,比如白名單,黑名單等場景
CopyOnWriteArrayList
由于add
時需要復制數組,所以不適用高性能的互聯網的應用。
CopyOnWriteArraySet
介紹public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<e>(); }
> CopyOnWriteArraySet
底層是用CopyOnWriteArraySet
來實現的。可變操作(add,set,remove等)都需要拷貝原數組進行操作,一般開銷很大。迭代器支持hasNext()
,netx()
等不可變操作,不支持可變的remove
操作,使用迭代器速度很快,并且不會與其它線程沖突,在構造迭代器時,依賴不變的數組快照。
CopyOnWriteArraySet
多線代碼演示package com.rumenz.task; import java.util.List; import java.util.Set; import java.util.concurrent.*; //線程安全 public class CopyOnWrireArraySetExample { public static Integer clientTotal=5000; public static Integer threadTotal=200; private static Set<integer> set=new CopyOnWriteArraySet(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final Integer j=i; executorService.execute(()->{ try{ semaphore.acquire(); update(j); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size:"+set.size()); } private static void update(Integer j) { set.add(j); } } //size:5000
CopyOnWriteArraySet
使用場景適用于set大小一般很小,讀操作遠遠多于寫操作的場景
ConcurrentSkipListSet
public ConcurrentSkipListSet() { m = new ConcurrentSkipListMap<e,object>(); }
> ConcurrentSkipListSet<e>
是jdk6
新增的類,支持自然排序,位于java.util.concurrent
。ConcurrentSkipListSet<e>
都是基于Map
集合的,底層由ConcurrentSkipListMap
實現。
> 在多線程環境下,ConcurrentSkipListSet<e>
的add
,remove
,contains
是線程安全的。但是對于批量操作addAll
,removeAll
,containsAll
并不能保證原子操作,所以是線程不安全的,原因是addAll
,removeAll
,containsAll
底層調用的還是add
,remove
,contains
方法,在批量操作時,只能保證每一次的add
,remove
,contains
是原子性的(即在進行add
,remove
,contains
,不會被其它線程打斷),而不能保證每一次批量操作都不會被其它線程打斷,因此在addAll
、removeAll
、retainAll
和 containsAll
操作時,需要添加額外的同步操作。
public boolean addAll(Collection<!--? extends E--> c) { boolean modified = false; for (E e : c) if (add(e)) modified = true; return modified; } public boolean removeAll(Collection<!--?--> c) { Objects.requireNonNull(c); boolean modified = false; Iterator<!--?--> it = iterator(); while (it.hasNext()) { if (c.contains(it.next())) { it.remove(); modified = true; } } return modified; } public boolean containsAll(Collection<!--?--> c) { for (Object e : c) if (!contains(e)) return false; return true; }
ConcurrentSkipListSet
代碼演示package com.rumenz.task; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.*; //線程安全 public class CopyOnWrireArrayListExample { public static Integer clientTotal=5000; public static Integer threadTotal=200; private static Set<integer> set= new ConcurrentSkipListSet(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final Integer j=i; executorService.execute(()->{ try{ semaphore.acquire(); update(j); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size:"+set.size()); } private static void update(Integer r) { set.add(r); } } //size:5000
ConcurrentHashMap
> ConcurrentHashMap
中key
和value
都不允許為null
,ConcurrentHashMap
針對讀操作做了大量的優化。在高并發場景很有優勢。
> 在多線程環境下,使用HashMap
進行put
操作會引起死循環,導致CPU
利用率到100%
,所以在多線程環境不能隨意使用HashMap
。原因分析:HashMap
在進行put
的時候,插入的元素超過了容量就會發生rehash
擴容,這個操作會把原來的元素hash
到新的擴容新的數組,在多線程情況下,如果此時有其它線程在進行put
操作,如果Hash
值相同,可能出現在同一數組下用鏈表表示,造成閉環,導致get
的時候出現死循環,所以是線程不安全的。
> HashTable
它是線程安全的,它涉及到多線程的操作都synchronized
關鍵字來鎖住整個table
,這就意味著所有的線程都在競爭同一把鎖,在多線程環境下是安全的,但是效率很低。
> HashTable
有很多的優化空間,鎖住整個table這么粗暴的方法可以變相的柔和點,比如在多線程的環境下,對不同的數據集進行操作時其實根本就不需要去競爭一個鎖,因為他們不同hash值,不會因為rehash造成線程不安全,所以互不影響,這就是鎖分離技術,將鎖的粒度降低,利用多個鎖來控制多個小的table,多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高并發訪問效率,這就是ConcurrentHashMapJDK1.7版本的核心思想。
ConcurrentHashMap
代碼演示案例package com.rumenz.task; import java.util.Map; import java.util.Set; import java.util.concurrent.*; //線程安全 public class ConcurrentHashMapExample { public static Integer clientTotal=5000; public static Integer threadTotal=200; private static Map<integer,integer> map=new ConcurrentHashMap<integer,integer>(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final Integer j=i; executorService.execute(()->{ try{ semaphore.acquire(); update(j); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size:"+map.size()); } private static void update(Integer j) { map.put(j, j); } } //size:5000
ConcurrentSkipListMap
> ConcurrentSkipListMap
內部使用SkipList
結構實現。跳表是一個鏈表,但是通過跳躍式的查找方式使得插入
,讀取
數據時的時間復雜度變成O(log n)
。
> 跳表(SkipList):使用空間換時間的算法,令鏈表的每個結點不僅記錄next結點位置,還可以按照level層級分別記錄后繼第level個結點。
ConcurrentSkipListMap
代碼案例package com.rumenz.task; import java.util.Map; import java.util.concurrent.*; //線程安全 public class ConcurrentSkipListMapExample { public static Integer clientTotal=5000; public static Integer threadTotal=200; private static Map<integer,integer> map=new ConcurrentSkipListMap<>(); public static void main(String[] args) throws Exception{ ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore=new Semaphore(threadTotal); final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final Integer j=i; executorService.execute(()->{ try{ semaphore.acquire(); update(j); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("size:"+map.size()); } private static void update(Integer j) { map.put(j, j); } } //size:5000
ConcurrentHashMap
與ConcurrentSkipListMap
的對比ConcurrentHashMap
比ConcurrentSkipListMap
性能要好一些。
ConcurrentSkipListMap
的key
是有序的,ConcurrentHashMap
做不到。
ConcurrentSkipListMap
支持高并發,它的時間復雜度是log(N)
,和線程數無關,也就是說任務一定的情況下,并發的線程越多,ConcurrentSkipListMap
的優勢就越能體現出來。
上述內容就是如何理解Java并發容器J.U.C,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。