<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>

          Java多線程之COW機(jī)制

          共 4107字,需瀏覽 9分鐘

           ·

          2021-10-20 21:15

          這里來介紹COW(Copy-on-write)寫時復(fù)制技術(shù),并闡述其在Java中的應(yīng)用

          abstract.jpg

          基本原理

          這里以Java集合為場景來解釋所謂的寫時復(fù)制COW(Copy-on-write)機(jī)制。當(dāng)向集合添加、移除元素時,并不是直接操作當(dāng)前集合,而是首先對原集合進(jìn)行拷貝、復(fù)制到一個新集合中。然后對該新集合進(jìn)行添加、移除元素的操作。最后,再將對原集合的引用指向新集合即可。該機(jī)制的好處就在于當(dāng)我們訪問、讀取集合是不需要加鎖的,即COW機(jī)制是讀寫分離思想的具體體現(xiàn)。當(dāng)然其弊端也是顯而易見的,一方面由于寫操作需要復(fù)制操作,所以對于較大規(guī)模的集合而言,其耗費的內(nèi)存也是較為可觀的;另一方面,由于讀操作訪問的是原集合,即使寫操作在此期間已經(jīng)完成。對于前者(讀操作)而言也是不可見的,即其保證的是最終一致性

          應(yīng)用實踐

          Java的ArrayList大家都很熟悉,其是線程不安全的。當(dāng)多個線程對其進(jìn)行操作時,由于fail-fast快速失敗機(jī)制的存在,其會盡可能拋出ConcurrentModificationException并發(fā)修改異常。為此針對數(shù)組列表,Java提供了一個線程安全的版本——Vector。其通過synchronized關(guān)鍵字實現(xiàn)了線程安全,故效率較低一般較少使用。為此Java還提供了一個基于COW機(jī)制的數(shù)組列表——CopyOnWriteArrayList。具體來說,該集合在進(jìn)行讀操作時是無鎖的。在進(jìn)行寫操作時,則通過ReentrantLock可重入鎖來保證當(dāng)前最多只有一個寫線程可以對其進(jìn)行復(fù)制、修改的操作

          此外Java還提供了另外一個基于COW機(jī)制的并發(fā)容器——CopyOnWriteArraySet。具體來說,其首先是一個Set,元素的唯一性與HashSet類似,也是通過元素的equals方法來實現(xiàn)的。其次,其底層數(shù)據(jù)結(jié)構(gòu)使用的是CopyOnWriteArrayList,即通過數(shù)組來存儲元素

          源碼分析

          CopyOnWriteArrayList的源碼實現(xiàn)還是比較簡單的。其一方面,通過ReentrantLock可重入鎖來保證當(dāng)前最多只有一個寫線程可以對其進(jìn)行復(fù)制、修改的操作;另一方面,通過volatile保障可見性,使得在寫線程結(jié)束后,后續(xù)的讀線程可以立即讀取到最新的數(shù)據(jù)。但在set(int index, E element)方法中,如果發(fā)現(xiàn)指定索引處的原數(shù)據(jù)與新數(shù)據(jù)一樣時,其卻存在一行看似沒必要的代碼——setArray(elements)。原數(shù)組的內(nèi)容雖然沒有發(fā)生變化,但卻依然讓volatile變量array再次指向原數(shù)組。但本來array也是指向該數(shù)組的啊,為什么要多此一舉呢?

          public?class?CopyOnWriteArrayList<E>?implements?List<E>,?RandomAccess,?Cloneable,?java.io.Serializable?{

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

          ????private?transient?volatile?Object[]?array;

          ????public?E?set(int?index,?E?element)?{
          ????????final?ReentrantLock?lock?=?this.lock;
          ????????lock.lock();
          ????????try?{
          ????????????Object[]?elements?=?getArray();
          ????????????E?oldValue?=?get(elements,?index);

          ????????????if?(oldValue?!=?element)?{
          ????????????????int?len?=?elements.length;
          ????????????????Object[]?newElements?=?Arrays.copyOf(elements,?len);
          ????????????????newElements[index]?=?element;
          ????????????????setArray(newElements);
          ????????????}?else?{
          ????????????????//?Not?quite?a?no-op;?ensures?volatile?write?semantics
          ????????????????setArray(elements);?????//?該行看似沒有必要,實則是?妙蛙種子在米奇妙妙屋吃妙脆角,妙到家了
          ????????????}
          ????????????return?oldValue;
          ????????}?finally?{
          ????????????lock.unlock();
          ????????}
          ????}

          ????final?Object[]?getArray()?{
          ????????return?array;
          ????}

          ????private?E?get(Object[]?a,?int?index)?{
          ????????return?(E)?a[index];
          ????}

          ????final?void?setArray(Object[]?a)?{
          ????????array?=?a;
          ????}

          ????...

          }

          在解答上述問題之前,我們需要先深刻理解下volatile關(guān)鍵字。在早期的Java內(nèi)存模型(JMM)中,雖然不允許volatile變量之間進(jìn)行重排序,但卻允許普通變量與volatile變量之間的重排序。由此導(dǎo)致程序發(fā)生讓人非常難以理解的現(xiàn)象及Bug,故在JSR 133中對volatile的內(nèi)存語義作了進(jìn)一步增強(qiáng),限制了普通變量與volatile變量之間重排序的場景。下面是一個普通變量與volatile變量重排序的例子。假設(shè)線程A調(diào)用writer方法,線程B調(diào)用reader方法。當(dāng)線程B看到volatile變量v為true時,則后續(xù)訪問普通變量x一定可以保證其是42。換言之,在線程A執(zhí)行writer方法過程中,(1)處代碼一定先于(2)處代碼執(zhí)行。而在早期的JMM中由于允許普通變量與volatile變量之間的重排序,會導(dǎo)致(2)處代碼會被重排序到(1)處代碼前面進(jìn)而先執(zhí)行。進(jìn)一步會導(dǎo)致線程B雖然已經(jīng)看到volatile變量v為true了,但訪問變量x值卻為0

          class?VolatileDemo?{
          ??
          ????int?x?=?0;
          ????
          ????volatile?boolean?v?=?false;
          ????
          ????//?寫操作
          ????public?void?writer()?{
          ????????x?=?42;?????//?(1)?普通變量寫
          ????????v?=?true;???//?(2)?valatile變量寫
          ????}

          ????//?讀操作
          ????public?void?reader()?{
          ????????if?(v?==?true)?{??//?(3)?valatile變量讀
          ????????????//?此時訪問變量x,?一定可以保證其值是?42
          ????????????...???//?(4)?普通變量讀
          ????????}
          ????}

          }

          現(xiàn)在回歸正題,我們看到在CopyOnWriteArrayList類上的注釋對Memory Consistency Effects內(nèi)存一致性影響所做出的保證:一個線程中放置一個元素到CopyOnWriteArrayList時其之前的動作 happen-before先行發(fā)生于 另外一個線程從CopyOnWriteArrayList訪問、刪除該元素時其之后的動作

          figure 1.jpg

          假設(shè)現(xiàn)在有個普通變量nonVolatileField其初值為0,有一個CopyOnWriteArrayList實例,其在0位置的元素為x。如果線程1、2分別按如下代碼執(zhí)行。按CopyOnWriteArrayList類的Memory Consistency Effects說明進(jìn)行分析,則當(dāng)線程2執(zhí)行到(4)處代碼時,線程1的(1)處代碼必然要已經(jīng)執(zhí)行完成了,即線程2需要看到普通變量nonVolatileField已經(jīng)為1了。那如何保證(1)、(2)處的代碼不會發(fā)生重排序,以避免出現(xiàn)(2)先執(zhí)行、(1)后執(zhí)行的局面呢?答案自然是讓(2)處代碼執(zhí)行對volatile變量的寫。這也就是為什么在set(int index, E element)方法中,即使原數(shù)組的內(nèi)容雖然沒有發(fā)生變化,但卻依然讓volatile變量array再次指向原數(shù)組。其目的是用于保證volatile變量的寫語義

          //?初始條件
          int?nonVolatileField?=?0;
          CopyOnWriteArrayList?list?=?new?CopyOnWriteArrayList<>();
          list.add("x");

          //?Thread?1
          nonVolatileField?=?1;?????????????????//?(1)
          list.set(0,?"x");?????????????????????//?(2)

          //?Thread?2
          String?s?=?list.get(0);???????????????//?(3)
          if?(s?==?"x")?{
          ????int?localVar?=?nonVolatileField;??//?(4)
          }

          參考文獻(xiàn)

          1. Java并發(fā)編程之美 翟陸續(xù)、薛賓田著
          2. 深入理解Java虛擬機(jī)·第2版 周志明著
          3. JSR-133: Java Memory Model and Thread Specification
          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  手机在线毛片 | 欧美内射视频网站 | 超碰在线日本 | 人人操人人吻人人射 | 看国产熟妇乱子伦 |