<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          看了CopyOnWriteArrayList后自己實(shí)現(xiàn)了一個(gè)CopyOnWriteHashMap

          共 6231字,需瀏覽 13分鐘

           ·

          2021-02-05 21:20

          Python實(shí)戰(zhàn)社群

          Java實(shí)戰(zhàn)社群

          長(zhǎng)按識(shí)別下方二維碼,按需求添加

          掃碼關(guān)注添加客服

          進(jìn)Python社群▲

          掃碼關(guān)注添加客服

          進(jìn)Java社群


          作者丨碼農(nóng)二胖

          來(lái)源丨java金融

          引言

          面試官: 小伙子你有點(diǎn)眼熟啊,是不是去年來(lái)這面試過(guò)啊。
          二胖: 啊,沒(méi)有啊我這是第一次來(lái)這。
          面試官: 行,那我們開(kāi)始今天的面試吧,剛開(kāi)始我們先來(lái)點(diǎn)簡(jiǎn)單的吧,java里面的容器你知道哪些啊,跟我說(shuō)一說(shuō)吧。
          二胖: 好的,java里面常見(jiàn)容器有ArrayList(線程非安全)、HashMap(線程非安全)、HashSet(線程非安全),ConcurrentHashMap(線程安全)。
          面試官: ArrayList 既然線程非安全那有沒(méi)有線程安全的ArrayList列?
          二胖: ?這個(gè)。。。好像問(wèn)到知識(shí)盲點(diǎn)了。
          面試官: ?那我們今天的面試就先到這了,我待會(huì)還有一個(gè)會(huì),后續(xù)如有通知人事會(huì)聯(lián)系你的。
          以上故事純屬虛構(gòu)如有雷同請(qǐng)以本文為主。

          什么是COW

          在java里面說(shuō)到集合容器我們一般首先會(huì)想到的是HashMap、ArrayListHasHSet這幾個(gè)容器也是平時(shí)開(kāi)發(fā)中用的最多的。這幾個(gè)都是非線程安全的,如果我們有特定業(yè)務(wù)需要使用線程的安全容器列,

          • HashMap可以用ConcurrentHashMap代替。
          • ArrayList 可以使用Collections.synchronizedList()方法(list 每個(gè)方法都用synchronized修飾) 或者使用Vector(現(xiàn)在基本也不用了,每個(gè)方法都用synchronized修飾) 或者使用CopyOnWriteArrayList 替代。
          • HasHSet 可以使用 Collections.synchronizedSet 或者使用CopyOnWriteArraySet來(lái)代替。(CopyOnWriteArraySet為什么不叫CopyOnWriteHashSet因?yàn)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">CopyOnWriteArraySet底層是采用CopyOnWriteArrayList來(lái)實(shí)現(xiàn)的) 我們可以看到CopyOnWriteArrayList在線程安全的容器里面多次出現(xiàn)。首先我們來(lái)看看什么是CopyOnWrite?Copy-On-Write簡(jiǎn)稱COW,是一種用于程序設(shè)計(jì)中的優(yōu)化策略。

          CopyOnWrite容器即寫(xiě)時(shí)復(fù)制的容器。通俗的理解是當(dāng)我們往一個(gè)容器添加元素的時(shí)候,不直接往當(dāng)前容器添加,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出一個(gè)新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是我們可以對(duì)CopyOnWrite容器進(jìn)行并發(fā)的讀,而不需要加鎖,因?yàn)楫?dāng)前容器不會(huì)添加任何元素。所以CopyOnWrite容器也是一種讀寫(xiě)分離的思想,讀和寫(xiě)不同的容器。

          為什么要引入COW

          防止ConcurrentModificationException異常

          在java里面我們?nèi)绻捎貌徽_的循環(huán)姿勢(shì)去遍歷List時(shí)候,如果一邊遍歷一邊修改拋出java.util.ConcurrentModificationException錯(cuò)誤的。

          ????????List?list?=?new?ArrayList<>();
          ????????list.add("張三");
          ????????list.add("java金融");
          ????????list.add("javajr.cn");
          ????????Iterator?iterator?=?list.iterator();
          ????????while(iterator.hasNext()){
          ????????????String?content?=?iterator.next();
          ????????????if("張三".equals(content))?{
          ????????????????list.remove(content);
          ????????????}

          ????????}

          上面這個(gè)栗子是會(huì)發(fā)生java.util.ConcurrentModificationException異常的,如果把ArrayList改為CopyOnWriteArrayList 是不會(huì)發(fā)生生異常的。

          線程安全的容器

          我們?cè)倏聪旅嬉粋€(gè)栗子一個(gè)線程往List里面添加數(shù)據(jù),一個(gè)線程循環(huán)list讀數(shù)據(jù)。

          ????List?list?=?new?ArrayList<>();
          ????????list.add("張三");
          ????????list.add("java金融");
          ????????list.add("javajr.cn");
          ????????Thread?t?=?new?Thread(new?Runnable()?{
          ????????????int?count?=?0;
          ????????????@Override
          ????????????public?void?run()?{
          ????????????????while?(true)?{
          ????????????????????list.add(count++?+?"");
          ????????????????}
          ????????????}
          ????????});
          ????????t.start();
          ????????Thread.sleep(10000);
          ????????for?(String?s?:?list)?{
          ????????????System.out.println(s);
          ????????}

          我們運(yùn)行上述代碼也會(huì)發(fā)生ConcurrentModificationException異常,如果把ArrayList換成了CopyOnWriteArrayList就一切正常。

          CopyOnWriteArrayList的實(shí)現(xiàn)

          通過(guò)上面兩個(gè)栗子我們可以發(fā)現(xiàn)CopyOnWriteArrayList是線程安全的,下面我們就來(lái)一起看看CopyOnWriteArrayList是如何實(shí)現(xiàn)線程安全的。

          public?class?CopyOnWriteArrayList<E>
          ????implements?List<E>,?RandomAccess,?Cloneable,?java.io.Serializable?
          {
          ????private?static?final?long?serialVersionUID?=?8673264195747942595L;

          ????/**?The?lock?protecting?all?mutators?*/
          ????final?transient?ReentrantLock?lock?=?new?ReentrantLock();

          ????/**?The?array,?accessed?only?via?getArray/setArray.?*/
          ????private?transient?volatile?Object[]?array;

          從源碼中我們可以知道CopyOnWriteArrayList這和ArrayList底層實(shí)現(xiàn)都是通過(guò)一個(gè)Object的數(shù)組來(lái)實(shí)現(xiàn)的,只不過(guò) CopyOnWriteArrayList的數(shù)組是通過(guò)volatile來(lái)修飾的。

          add方法:

          ????public?boolean?add(E?e)?{
          ????????//?先獲取鎖
          ????????final?ReentrantLock?lock?=?this.lock;
          ????????lock.lock();
          ????????try?{
          ????????????Object[]?elements?=?getArray();
          ????????????int?len?=?elements.length;
          ????????????//?復(fù)制一個(gè)新的數(shù)組
          ????????????Object[]?newElements?=?Arrays.copyOf(elements,?len?+?1);
          ????????????newElements[len]?=?e;
          ????????????//?把新數(shù)組的值?賦給原數(shù)組
          ????????????setArray(newElements);
          ????????????return?true;
          ????????}?finally?{
          ????????????//?釋放鎖
          ????????????lock.unlock();
          ????????}
          ????}

          上述源碼我們可以發(fā)現(xiàn)比較簡(jiǎn)單,有幾個(gè)點(diǎn)需要稍微注意下

          • 增加數(shù)據(jù)的時(shí)候是通過(guò)ReentrantLock加鎖操作來(lái)(在jdk11的時(shí)候采用了synchronized來(lái)替換ReentrantLock)保證多線程寫(xiě)的時(shí)候只有一個(gè)線程進(jìn)行數(shù)組的復(fù)制,否則的話內(nèi)存中會(huì)有多份被復(fù)制的數(shù)據(jù),導(dǎo)致數(shù)據(jù)錯(cuò)亂。
          • 數(shù)組是通過(guò)volatile 修飾的,根據(jù) volatilehappens-before 規(guī)則,寫(xiě)線程對(duì)數(shù)組引用的修改是可以立即對(duì)讀線程是可見(jiàn)的。
          • 通過(guò)寫(xiě)時(shí)復(fù)制來(lái)保證讀寫(xiě)在兩個(gè)不同的數(shù)據(jù)容器中進(jìn)行操作。

          自己實(shí)現(xiàn)一個(gè)COW容器

          Java并發(fā)包里提供了兩個(gè)使用CopyOnWrite機(jī)制實(shí)現(xiàn)的并發(fā)容器,它們是CopyOnWriteArrayListCopyOnWriteArraySet,但是并沒(méi)有CopyOnWriteHashMap我們可以按照他的思路自己來(lái)實(shí)現(xiàn)一個(gè)CopyOnWriteHashMap

          public?class?CopyOnWriteHashMap<K,?V>?implements?Map<K,?V>,?Cloneable?{

          ????final?transient?ReentrantLock?lock?=?new?ReentrantLock();

          ????private?volatile?Map?map;


          ????public?CopyOnWriteHashMap()?{
          ????????map?=?new?HashMap<>();
          ????}

          ????@Override
          ????public?V?put(K?key,?V?value)?{
          ????????final?ReentrantLock?lock?=?this.lock;
          ????????lock.lock();
          ????????try?{
          ????????????Map?newMap?=?new?HashMap(map);
          ????????????V?val?=?newMap.put(key,?value);
          ????????????map?=?newMap;
          ????????????return?val;
          ????????}?finally?{
          ????????????lock.unlock();
          ????????}
          ????}

          ????@Override
          ????public?V?get(Object?key)?{
          ????????return?map.get(key);
          ????}
          ????@Override
          ????public?V?remove(Object?key)?{
          ????????final?ReentrantLock?lock?=?this.lock;
          ????????lock.lock();
          ????????try?{
          ????????????Map?newMap?=?new?HashMap(map);

          ????????????if?(!newMap.containsKey(key))?{
          ????????????????return?null;
          ????????????}
          ????????????V?v?=?newMap.get(key);
          ????????????newMap.remove(key);
          ????????????map?=?newMap;
          ????????????return?v;
          ????????}finally?{
          ????????????lock.unlock();
          ????????}
          ????}

          上述我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的CopyOnWriteHashMap,只實(shí)現(xiàn)了add、remove、get方法其他剩余的方法可以自行去實(shí)現(xiàn),涉及到只要數(shù)據(jù)變化的就要加鎖,讀無(wú)需加鎖。

          應(yīng)用場(chǎng)景

          CopyOnWrite并發(fā)容器適用于讀多寫(xiě)少的并發(fā)場(chǎng)景,比如黑白名單、國(guó)家城市等基礎(chǔ)數(shù)據(jù)緩存、系統(tǒng)配置等。這些基本都是只要想項(xiàng)目啟動(dòng)的時(shí)候初始化一次,變更頻率非常的低。如果這種讀多寫(xiě)少的場(chǎng)景采用 Vector,Collections包裝的這些方式是不合理的,因?yàn)楸M管多個(gè)讀線程從同一個(gè)數(shù)據(jù)容器中讀取數(shù)據(jù),但是讀線程對(duì)數(shù)據(jù)容器的數(shù)據(jù)并不會(huì)發(fā)生發(fā)生修改,所以并不需要讀也加鎖。

          CopyOnWrite缺點(diǎn)

          CopyOnWriteArrayList雖然是一個(gè)線程安全版的ArrayList,但其每次修改數(shù)據(jù)時(shí)都會(huì)復(fù)制一份數(shù)據(jù)出來(lái),所以CopyOnWriteArrayList只適用讀多寫(xiě)少或無(wú)鎖讀場(chǎng)景。我們?nèi)绻趯?shí)際業(yè)務(wù)中使用CopyOnWriteArrayList,一定是因?yàn)檫@個(gè)場(chǎng)景適合而非是為了炫技。

          內(nèi)存占用問(wèn)題

          因?yàn)镃opyOnWrite的寫(xiě)時(shí)復(fù)制機(jī)制每次進(jìn)行寫(xiě)操作的時(shí)候都會(huì)有兩個(gè)數(shù)組對(duì)象的內(nèi)存,如果這個(gè)數(shù)組對(duì)象占用的內(nèi)存較大的話,如果頻繁的進(jìn)行寫(xiě)入就會(huì)造成頻繁的Yong GC和Full GC。

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

          CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實(shí)時(shí)一致性。讀操作的線程可能不會(huì)立即讀取到新修改的數(shù)據(jù),因?yàn)樾薷牟僮靼l(fā)生在副本上。但最終修改操作會(huì)完成并更新容器所以這是最終一致性。

          CopyOnWriteArrayList和Collections.synchronizedList()

          簡(jiǎn)單的測(cè)試了下CopyOnWriteArrayList ?和 ?Collections.synchronizedList()的讀和寫(xiě)發(fā)現(xiàn):

          • 在高并發(fā)的寫(xiě)時(shí)CopyOnWriteArray比同步Collections.synchronizedList慢百倍
          • 在高并發(fā)的讀性能時(shí)CopyOnWriteArray比同步Collections.synchronizedList快幾十倍。
          • 高并發(fā)寫(xiě)時(shí),CopyOnWriteArrayList為何這么慢呢?因?yàn)槠涿看蝍dd時(shí),都用Arrays.copyOf創(chuàng)建新數(shù)組,頻繁add時(shí)內(nèi)存申請(qǐng)釋放性能消耗大。
          • 高并發(fā)讀的時(shí)候CopyOnWriteArray無(wú)鎖,Collections.synchronizedList有鎖所以讀的效率比較低下。

          總結(jié)

          選擇CopyOnWriteArrayList的時(shí)候一定是讀遠(yuǎn)大于寫(xiě)。如果讀寫(xiě)都差不多的話建議選擇Collections.synchronizedList。

          巨人肩膀摘蘋(píng)果

          http://ifeve.com/java-copy-on-write/

          程序員專欄
          ?掃碼關(guān)注填加客服?
          長(zhǎng)按識(shí)別下方二維碼進(jìn)群

          近期精彩內(nèi)容推薦:??

          ?幾句話,離職了

          ?中國(guó)男性的私密數(shù)據(jù)大賞,女生勿入!

          ?為什么很多人用“ji32k7au4a83”作密碼?

          ?一個(gè)月薪 12000 的北京程序員的真實(shí)生活 !




          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 13
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美操逼视频 青青操 | 无码三级麻豆 | 做爰 视频毛片下载蜜桃 | 黄色片免费播放 | 逼操逼网 |