本篇文章為大家展示了如何理解Java并發(fā)容器J.U.C,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
十載創(chuàng)新互聯(lián)網(wǎng)站建設(shè),由一走到現(xiàn)在,當中離不開團隊頑強的創(chuàng)業(yè)精神,離不開伴隨我們同行的客戶與專業(yè)的合作伙伴,創(chuàng)力信息一直秉承以“見一個客戶,了解一個行業(yè),交一個朋友”的方式為經(jīng)營理念,提出“讓每一個客戶成為我們的終身客戶”為目標,以為用戶提供精細化服務,全面滿足用戶需求為宗旨,誠信經(jīng)營,更大限度為用戶創(chuàng)造價值。期待邁向下一個更好的十載。
> J.U.C
是java.util.concurrent
的簡寫,里面提供了很多線程安全的集合。
CopyOnWriteArrayList
介紹> CopyOnWriteArrayList
相比于ArrayList
是線程安全的,字面意思是寫操作時復制
。CopyOnWriteArrayList
使用寫操作時復制
技術(shù),當有新元素需要加入時,先從原數(shù)組拷貝一份出來。然后在新數(shù)組里面加鎖添加,添加之后,將原來數(shù)組的引用指向新數(shù)組。
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
并發(fā)add
時,復制多個副本,把數(shù)據(jù)搞亂。
public E get(int index) { return get(getArray(), index); }
> 以上代碼顯示get
是沒有加鎖的
> 如果出現(xiàn)并發(fā)get
,會有以下3中情況。
如果寫操作未完成,那么直接讀取原數(shù)組的數(shù)據(jù);
如果寫操作完成,但是引用還未指向新數(shù)組,那么也是讀取原數(shù)組數(shù)據(jù);
如果寫操作完成,并且引用已經(jīng)指向了新的數(shù)組,那么直接從新數(shù)組中讀取數(shù)據(jù)。
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
的時候需要拷貝原數(shù)組,如果原數(shù)組內(nèi)容比較多,比較大,可能會導致young gc
和full gc
。
不能用于實時讀的場景,像拷貝數(shù)組,新增元素都需要時間,所以調(diào)用get
操作后,有可能得到的數(shù)據(jù)是舊數(shù)據(jù),雖然CopyOnWriteArrayList
能做到最終一致性,但是沒有辦法滿足實時性要求。
CopyOnWriteArrayList
適合讀多寫少的場景,比如白名單,黑名單等場景
CopyOnWriteArrayList
由于add
時需要復制數(shù)組,所以不適用高性能的互聯(lián)網(wǎng)的應用。
CopyOnWriteArraySet
介紹public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<e>(); }
> CopyOnWriteArraySet
底層是用CopyOnWriteArraySet
來實現(xiàn)的。可變操作(add,set,remove等)都需要拷貝原數(shù)組進行操作,一般開銷很大。迭代器支持hasNext()
,netx()
等不可變操作,不支持可變的remove
操作,使用迭代器速度很快,并且不會與其它線程沖突,在構(gòu)造迭代器時,依賴不變的數(shù)組快照。
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
實現(xiàn)。
> 在多線程環(huán)境下,ConcurrentSkipListSet<e>
的add
,remove
,contains
是線程安全的。但是對于批量操作addAll
,removeAll
,containsAll
并不能保證原子操作,所以是線程不安全的,原因是addAll
,removeAll
,containsAll
底層調(diào)用的還是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
針對讀操作做了大量的優(yōu)化。在高并發(fā)場景很有優(yōu)勢。
> 在多線程環(huán)境下,使用HashMap
進行put
操作會引起死循環(huán),導致CPU
利用率到100%
,所以在多線程環(huán)境不能隨意使用HashMap
。原因分析:HashMap
在進行put
的時候,插入的元素超過了容量就會發(fā)生rehash
擴容,這個操作會把原來的元素hash
到新的擴容新的數(shù)組,在多線程情況下,如果此時有其它線程在進行put
操作,如果Hash
值相同,可能出現(xiàn)在同一數(shù)組下用鏈表表示,造成閉環(huán),導致get
的時候出現(xiàn)死循環(huán),所以是線程不安全的。
> HashTable
它是線程安全的,它涉及到多線程的操作都synchronized
關(guān)鍵字來鎖住整個table
,這就意味著所有的線程都在競爭同一把鎖,在多線程環(huán)境下是安全的,但是效率很低。
> HashTable
有很多的優(yōu)化空間,鎖住整個table這么粗暴的方法可以變相的柔和點,比如在多線程的環(huán)境下,對不同的數(shù)據(jù)集進行操作時其實根本就不需要去競爭一個鎖,因為他們不同hash值,不會因為rehash造成線程不安全,所以互不影響,這就是鎖分離技術(shù),將鎖的粒度降低,利用多個鎖來控制多個小的table,多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù)時,線程間就不會存在鎖競爭,從而可以有效的提高并發(fā)訪問效率,這就是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
內(nèi)部使用SkipList
結(jié)構(gòu)實現(xiàn)。跳表是一個鏈表,但是通過跳躍式的查找方式使得插入
,讀取
數(shù)據(jù)時的時間復雜度變成O(log n)
。
> 跳表(SkipList):使用空間換時間的算法,令鏈表的每個結(jié)點不僅記錄next結(jié)點位置,還可以按照level層級分別記錄后繼第level個結(jié)點。
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
支持高并發(fā),它的時間復雜度是log(N)
,和線程數(shù)無關(guān),也就是說任務一定的情況下,并發(fā)的線程越多,ConcurrentSkipListMap
的優(yōu)勢就越能體現(xiàn)出來。
上述內(nèi)容就是如何理解Java并發(fā)容器J.U.C,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
當前名稱:如何理解Java并發(fā)容器J.U.C
網(wǎng)址分享:http://jinyejixie.com/article32/jjhjpc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計、Google、網(wǎng)站改版、網(wǎng)站維護、云服務器、做網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)