日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java-并发-并发容器(3)

發(fā)布時間:2025/3/20 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java-并发-并发容器(3) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

同樣注意內(nèi)層的第一個for循環(huán),里面有語句int c = segments[i].count; 但是c卻從來沒有被使用過,即使如此,編譯器也不能做優(yōu)化將這條語句去掉,因為存在對volatile變量count的讀取,這條語句存在的唯一目的就是保證segments[i].modCount讀取到幾乎最新的值。關于containsValue方法的其它部分就不分析了,它和size方法差不多。

跨段方法中還有一個isEmpty()方法,其實現(xiàn)比size()方法還要簡單,也不介紹了。最后簡單地介紹下迭代方法,如keySet(), values(), entrySet()方法,這些方法都返回相應的迭代器,所有迭代器都繼承于Hash_Iterator類(提交時居然提醒我不能包含sh It,只得加了下劃線),里實現(xiàn)了主要的方法。其結構是:

abstract class Hash_Iterator{ int nextSegmentIndex; int nextTableIndex; HashEntry<K,V>[] currentTable; HashEntry<K, V> nextEntry; HashEntry<K, V> lastReturned; }

nextSegmentIndex是段的索引,nextTableIndex是nextSegmentIndex對應段中中hash鏈的索引,currentTable是nextSegmentIndex對應段的table。調(diào)用next方法時主要是調(diào)用了advance方法:

final void advance() { if (nextEntry != null && (nextEntry = nextEntry.next) != null) return; while (nextTableIndex >= 0) { if ( (nextEntry = currentTable[nextTableIndex--]) != null) return; } while (nextSegmentIndex >= 0) { Segment<K,V> seg = segments[nextSegmentIndex--]; if (seg.count != 0) { currentTable = seg.table; for (int j = currentTable.length - 1; j >= 0; --j) { if ( (nextEntry = currentTable[j]) != null) { nextTableIndex = j - 1; return; } } } } }

不想再多介紹了,唯一需要注意的是跳到下一個段時,一定要先讀取下一個段的count變量。

這種迭代方式的主要效果是不會拋出ConcurrentModificationException。一旦獲取到下一個段的table,也就意味著這個段的頭結點在迭代過程中就確定了,在迭代過程中就不能反映對這個段節(jié)點并發(fā)的刪除和添加,對于節(jié)點的更新是能夠反映的,因為節(jié)點的值是一個volatile變量。
結束語

ConcurrentHashMap是一個支持高并發(fā)的高性能的HashMap實現(xiàn),它支持完全并發(fā)的讀以及一定程度并發(fā)的寫。ConcurrentHashMap的實現(xiàn)也是很精巧,充分利用了最新的JMM規(guī)范,值得學習,卻不值得模仿。
1.8 的版本,與JDK6的版本有很大的差異。實現(xiàn)線程安全的思想也已經(jīng)完全變了,它摒棄了Segment(鎖段)的概念,而是啟用了一種全新的方式實現(xiàn),利用CAS算法。它沿用了與它同時期的HashMap版本的思想,底層依然由“數(shù)組”+鏈表+紅黑樹的方式思想,但是為了做到并發(fā),又增加了很多輔助的類,例如TreeBin,Traverser等對象內(nèi)部類。CAS算法實現(xiàn)無鎖化的修改值的操作,他可以大大降低鎖代理的性能消耗。這個算法的基本思想就是不斷地去比較當前內(nèi)存中的變量值與你指定的一個變量值是否相等,如果相等,則接受你指定的修改的值,否則拒絕你的操作。因為當前線程中的值已經(jīng)不是最新的值,你的修改很可能會覆蓋掉其他線程修改的結果。這一點與樂觀鎖,SVN的思想是比較類似的。

List類型的CopyOnWriteArrayList

對應的非并發(fā)容器:ArrayList
目標:代替Vector、synchronizedList
原理:利用高并發(fā)往往是讀多寫少的特性,對讀操作不加鎖,對寫操作,先復制一份新的集合,在新的集合上面修改,然后將新集合賦值給舊的引用,并通過volatile 保證其可見性,當然寫操作的鎖是必不可少的了。
CopyOnWriteArrayList采用寫入時復制的方式避開并發(fā)問題。這其實是通過冗余和不可變性來解決并發(fā)問題,在性能上會有比較大的代價,但如果寫入的操作遠遠小于迭代和讀操作,那么性能就差別不大了。
應用它們的場合通常在數(shù)組相對較小,并且遍歷操作的數(shù)量大大超過可變操作的數(shù)量時,這種場合應用它們非常好。它們所有可變的操作都是先取得后臺數(shù)組的副本,對副本進行更改,然后替換副本,這樣可以保證永遠不會拋出ConcurrentModificationException移除。
CopyOnWriteArrayList容器是Collections.synchronizedList(List list)的替代方案,CopyOnWriteArrayList在某些情況下具有更好的性能,考慮讀遠大于寫的場景,如果把所有的讀操作進行加鎖,因為只有一個讀線程能夠獲得鎖,所以其他的讀線程都必須等待,大大影響性能。CopyOnWriteArrayList稱為“寫時復制”容器,就是在多線程操作容器對象時,把容器復制一份,這樣在線程內(nèi)部的修改就與其他線程無關了,而且這樣設計可以做到不阻塞其他的讀線程。從JDK1.5開始Java并發(fā)包里提供了兩個使用CopyOnWrite機制實現(xiàn)的并發(fā)容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWriteArrayList源碼
先說說CopyOnWriteArrayList容器的實現(xiàn)原理:簡單地說,就是在需要對容器進行操作的時候,將容器拷貝一份,對容器的修改等操作都在容器的拷貝中進行,當操作結束,再把容器容器的拷貝指向原來的容器。這樣設計的好處是實現(xiàn)了讀寫分離,并且讀讀不會發(fā)生阻塞。下面的源碼是CopyOnWriteArrayList的add方法實現(xiàn):

public void add(int index, E element) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;if (index > len || index < 0)throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len);Object[] newElements;int numMoved = len - index;//1、復制出一個新的數(shù)組if (numMoved == 0)newElements = Arrays.copyOf(elements, len + 1);else {newElements = new Object[len + 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index, newElements, index + 1,numMoved);}//2、把新元素添加到新數(shù)組中newElements[index] = element;//3、把數(shù)組指向原來的數(shù)組setArray(newElements);} finally {lock.unlock();}}

上面的三個步驟實現(xiàn)了寫時復制的思想,在讀數(shù)據(jù)的時候不會鎖住list,因為寫操作是在原來容器的拷貝上進行的。而且,可以看到,如果對容器拷貝修改的過程中又有新的讀線程進來,那么讀到的還是舊的數(shù)據(jù)。讀的代碼如下

public E get(int index) {return get(getArray(), index);}final Object[] getArray() {return array;}

CopyOnWrite并發(fā)容器用于讀多寫少的并發(fā)場景。比如白名單,黑名單,商品類目的訪問和更新場景
從CopyOnWriteArrayList的實現(xiàn)原理可以看到因為在需要容器對象的時候進行拷貝,所以存在兩個問題:內(nèi)存占用問題和數(shù)據(jù)一致性問題。

內(nèi)存占用問題

因為需要將原來的對象進行拷貝,這需要一定的開銷。特別是當容器對象過大的時候,因為拷貝而占用的內(nèi)存將增加一倍(原來駐留在內(nèi)存的對象仍然在使用,拷貝之后就有兩份對象在內(nèi)存中,所以增加了一倍內(nèi)存)。而且,在高并發(fā)的場景下,因為每個線程都拷貝一份對象在內(nèi)存中,這種情況體現(xiàn)得更明顯。由于JVM的優(yōu)化機制,將會觸發(fā)頻繁的Young GC和Full GC,從而使整個系統(tǒng)的性能下降。

數(shù)據(jù)一致性問題

CopyOnWriteArrayList不能保證實時一致性,因為讀線程在將引用重新指向原來的對象之前再次讀到的數(shù)據(jù)是舊的,所以CopyOnWriteArrayList只能保證最終一致性。因此在需要實時一致性的廠幾個CopyOnWriteArrayList是不能使用的
CopyOnWriteArrayList適用于讀多寫少的場景
在并發(fā)操作容器對象時不會拋出ConcurrentModificationException,并且返回的元素與迭代器創(chuàng)建時的元素是一致的
容器對象的復制需要一定的開銷,如果對象占用內(nèi)存過大,可能造成頻繁的YoungGC和Full GC
CopyOnWriteArrayList不能保證數(shù)據(jù)實時一致性,只能保證最終一致性。
CopyOnWriteArrayList容器是Collections.synchronizedList(List list)的替代方案,CopyOnWriteArrayList在某些情況下具有更好的性能,考慮讀遠大于寫的場景,如果把所有的讀操作進行加鎖,因為只有一個讀線程能夠獲得鎖,所以其他的讀線程都必須等待,大大影響性能。CopyOnWriteArrayList稱為“寫時復制”容器,就是在多線程操作容器對象時,把容器復制一份,這樣在線程內(nèi)部的修改就與其他線程無關了,而且這樣設計可以做到不阻塞其他的讀線程。從JDK1.5開始Java并發(fā)包里提供了兩個使用CopyOnWrite機制實現(xiàn)的并發(fā)容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWriteArrayList容器使用示例

import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong;/*** Created by rhwayfun on 16-4-8.*/ public class CopyOnWriteArrayListDdemo {/*** 內(nèi)容編號*/private static AtomicLong contentNum;/*** 日期格式器*/private static DateFormat format;/*** 線程池*/private final ExecutorService threadPool;public CopyOnWriteArrayListDdemo() {contentNum = new AtomicLong();format = new SimpleDateFormat("HH:mm:ss");threadPool = Executors.newFixedThreadPool(10);}public void doExec(int num) throws InterruptedException {List<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i < num; i++){list.add(i,"main-content-" + i);}//5個寫線程for (int i = 0; i < 5; i++){threadPool.execute(new Writer(list,i));}//啟動10個讀線程for (int i = 0; i < 10; i++){threadPool.execute(new Reader(list));}//關閉線程池threadPool.shutdown();}/*** 寫線程** @author rhwayfun*/static class Writer implements Runnable {private final List<String> copyOnWriteArrayList;private int i;public Writer(List<String> copyOnWriteArrayList,int i) {this.copyOnWriteArrayList = copyOnWriteArrayList;this.i = i;}@Overridepublic void run() {copyOnWriteArrayList.add(i,"content-" + contentNum.incrementAndGet());System.out.println(Thread.currentThread().getName() + ": write content-" + contentNum.get()+ " " +format.format(new Date()));System.out.println(Thread.currentThread().getName() + ": remove " + copyOnWriteArrayList.remove(i));}}static class Reader implements Runnable {private final List<String> list;public Reader(List<String> list) {this.list = list;}@Overridepublic void run() {for (String s : list) {System.out.println(Thread.currentThread().getName() + ": read " + s+ " " +format.format(new Date()));}}}public static void main(String[] args) throws InterruptedException {CopyOnWriteArrayListDdemo demo = new CopyOnWriteArrayListDdemo();demo.doExec(5);} }

首先啟動5個寫線程,再啟動10個讀線程,運行該程序發(fā)現(xiàn)并沒有出現(xiàn)異常,所以使用寫時復制容器效率很高。代碼的運行結果如下:
先說說CopyOnWriteArrayList容器的實現(xiàn)原理:簡單地說,就是在需要對容器進行操作的時候,將容器拷貝一份,對容器的修改等操作都在容器的拷貝中進行,當操作結束,再把容器容器的拷貝指向原來的容器。這樣設計的好處是實現(xiàn)了讀寫分離,并且讀讀不會發(fā)生阻塞。下面的源碼是CopyOnWriteArrayList的add方法實現(xiàn):

public void add(int index, E element) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;if (index > len || index < 0)throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len);Object[] newElements;int numMoved = len - index;//1、復制出一個新的數(shù)組if (numMoved == 0)newElements = Arrays.copyOf(elements, len + 1);else {newElements = new Object[len + 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index, newElements, index + 1,numMoved);}//2、把新元素添加到新數(shù)組中newElements[index] = element;//3、把數(shù)組指向原來的數(shù)組setArray(newElements);} finally {lock.unlock();}}

上面的三個步驟實現(xiàn)了寫時復制的思想,在讀數(shù)據(jù)的時候不會鎖住list,因為寫操作是在原來容器的拷貝上進行的。而且,可以看到,如果對容器拷貝修改的過程中又有新的讀線程進來,那么讀到的還是舊的數(shù)據(jù)。讀的代碼如下:

public E get(int index) {return get(getArray(), index);}final Object[] getArray() {return array;}

總結

以上是生活随笔為你收集整理的java-并发-并发容器(3)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。