成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java多線程進階(二七)—— J.U.C之collections框架:CopyOnWriteArr

garfileo / 1129人閱讀

摘要:僅僅當有多個線程同時進行寫操作時,才會進行同步。可以看到,上述方法返回一個迭代器對象,的迭代是在舊數(shù)組上進行的,當創(chuàng)建迭代器的那一刻就確定了,所以迭代過程中不會拋出并發(fā)修改異常。另外,迭代器對象也不支持修改方法,全部會拋出異常。

本文首發(fā)于一世流云專欄:https://segmentfault.com/blog...
一、CopyOnWriteArrayList簡介

ArrayList是一種“列表”數(shù)據(jù)機構,其底層是通過數(shù)組來實現(xiàn)元素的隨機訪問。JDK1.5之前,如果想要在并發(fā)環(huán)境下使用“列表”,一般有以下3種方式:

使用Vector

使用Collections.synchronizedList返回一個同步代理類;

自己實現(xiàn)ArrayList的子類,并進行同步/加鎖。

前兩種方式都相當于加了一把“全局鎖”,訪問任何方法都需要首先獲取鎖。第3種方式,需要自己實現(xiàn),復雜度較高。

JDK1.5時,隨著J.U.C引入了一個新的集合工具類——CopyOnWriteArrayList

大多數(shù)業(yè)務場景都是一種“讀多寫少”的情形,CopyOnWriteArrayList就是為適應這種場景而誕生的。

CopyOnWriteArrayList,運用了一種“寫時復制”的思想。通俗的理解就是當我們需要修改(增/刪/改)列表中的元素時,不直接進行修改,而是先將列表Copy,然后在新的副本上進行修改,修改完成之后,再將引用從原列表指向新列表。

這樣做的好處是讀/寫是不會沖突的,可以并發(fā)進行,讀操作還是在原列表,寫操作在新列表。僅僅當有多個線程同時進行寫操作時,才會進行同步。

二、CopyOnWriteArrayList原理 內(nèi)部結構

CopyOnWriteArrayList的字段很簡單:

public class CopyOnWriteArrayList
    implements List, RandomAccess, Cloneable, java.io.Serializable {

    /**
     * 排它鎖, 用于同步修改操作
     */
    final transient ReentrantLock lock = new ReentrantLock();

    /**
     * 內(nèi)部數(shù)組
     */
    private transient volatile Object[] array;
}

其中,lock用于對修改操作進行同步,array就是內(nèi)部實際保存數(shù)據(jù)的數(shù)組。

構造器定義

CopyOnWriteArrayList提供了三種不同的構造器,這三種構造器最終都是創(chuàng)建一個數(shù)組,并通過setArray方法賦給array字段:

/**
 * 空構造器.
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
?
僅僅是設置一個了大小為0的數(shù)組,并賦給字段array:
final void setArray(Object[] a) {
    array = a;
}
/**
 * 根據(jù)已有集合創(chuàng)建
 */
public CopyOnWriteArrayList(Collection c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList) c).getArray();
    else {
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}
/**
 * 根據(jù)已有數(shù)組創(chuàng)建.
 *
 * @param toCopyIn the array (a copy of this array is used as the
 *                 internal array)
 * @throws NullPointerException if the specified array is null
 */
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
核心方法

查詢——get方法

public E get(int index) {
    return get(getArray(), index);
}

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

可以看到,get方法并沒有加鎖,直接返回了內(nèi)部數(shù)組對應索引位置的值:array[index]

添加——add方法

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();     // 舊數(shù)組
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);    // 復制并創(chuàng)建新數(shù)組
        newElements[len] = e;               // 將元素插入到新數(shù)組末尾
        setArray(newElements);              // 內(nèi)部array引用指向新數(shù)組
        return true;
    } finally {
        lock.unlock();
    }
}

add方法首先會進行加鎖,保證只有一個線程能進行修改;然后會創(chuàng)建一個新數(shù)組(大小為n+1),并將原數(shù)組的值復制到新數(shù)組,新元素插入到新數(shù)組的最后;最后,將字段array指向新數(shù)組。

上圖中,ThreadB對Array的修改由于是在新數(shù)組上進行的,所以并不會對ThreadA的讀操作產(chǎn)生影響。

刪除——remove方法

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);  // 獲取舊數(shù)組中的元素, 用于返回
        int numMoved = len - index - 1;     // 需要移動多少個元素
        if (numMoved == 0)                  // index位置剛好是最后一個元素
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index, numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

刪除方法和插入一樣,都需要先加鎖(所有涉及修改元素的方法都需要先加鎖,寫-寫不能并發(fā)),然后構建新數(shù)組,復制舊數(shù)組元素至新數(shù)組,最后將array指向新數(shù)組。

其它統(tǒng)計方法

public int size() {
    return getArray().length;
}

public boolean isEmpty() {
    return size() == 0;
}

迭代

CopyOnWriteArrayList對元素進行迭代時,僅僅返回一個當前內(nèi)部數(shù)組的快照,也就是說,如果此時有其它線程正在修改元素,并不會在迭代中反映出來,因為修改都是在新數(shù)組中進行的。

public Iterator iterator() {
    return new COWIterator(getArray(), 0);
}
?
static final class COWIterator implements ListIterator {
    /**
     * Snapshot of the array
     */
    private final Object[] snapshot;
    /**
     * Index of element to be returned by subsequent call to next.
     */
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    public E next() {
        if (!hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
    
    // ...
}

可以看到,上述iterator方法返回一個迭代器對象——COWIterator,COWIterator的迭代是在舊數(shù)組上進行的,當創(chuàng)建迭代器的那一刻就確定了,所以迭代過程中不會拋出并發(fā)修改異常——ConcurrentModificationException。

另外,迭代器對象也不支持修改方法,全部會拋出UnsupportedOperationException異常。

三、總結

CopyOnWriteArrayList的思想和實現(xiàn)整體上還是比較簡單,它適用于處理“讀多寫少”的并發(fā)場景。通過上述對CopyOnWriteArrayList的分析,讀者也應該可以發(fā)現(xiàn)該類存在的一些問題:

1. 內(nèi)存的使用
由于CopyOnWriteArrayList使用了“寫時復制”,所以在進行寫操作的時候,內(nèi)存里會同時存在兩個array數(shù)組,如果數(shù)組內(nèi)存占用的太大,那么可能會造成頻繁GC,所以CopyOnWriteArrayList并不適合大數(shù)據(jù)量的場景。

2. 數(shù)據(jù)一致性
CopyOnWriteArrayList只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實時一致性——讀操作讀到的數(shù)據(jù)只是一份快照。所以如果希望寫入的數(shù)據(jù)可以立刻被讀到,那CopyOnWriteArrayList并不適合。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/76940.html

相關文章

  • Java線程進階(二八)—— J.U.Ccollections框架CopyOnWriteArr

    摘要:我們之前已經(jīng)介紹過了,底層基于跳表實現(xiàn),其操作平均時間復雜度均為。事實上,內(nèi)部引用了一個對象,以組合方式,委托對象實現(xiàn)了所有功能。線程安全內(nèi)存的使用較多迭代是對快照進行的,不會拋出,且迭代過程中不支持修改操作。 showImg(https://segmentfault.com/img/bVbggjf?w=600&h=377); 本文首發(fā)于一世流云專欄:https://segmentfa...

    NeverSayNever 評論0 收藏0
  • Java線程進階(一)—— J.U.C并發(fā)包概述

    摘要:整個包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執(zhí)行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據(jù)一系列常見的多線程設計模式,設計了并發(fā)包,其中包下提供了一系列基礎的鎖工具,用以對等進行補充增強。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發(fā)于一世流云專欄:https...

    anonymoussf 評論0 收藏0
  • Java線程進階(二六)—— J.U.Ccollections框架:ConcurrentSkip

    摘要:我們來看下的類繼承圖可以看到,實現(xiàn)了接口,在多線程進階二五之框架中,我們提到過實現(xiàn)了接口,以提供和排序相關的功能,維持元素的有序性,所以就是一種為并發(fā)環(huán)境設計的有序工具類。唯一的區(qū)別是針對的僅僅是鍵值,針對鍵值對進行操作。 showImg(https://segmentfault.com/img/bVbggic?w=960&h=600); 本文首發(fā)于一世流云專欄:https://seg...

    levius 評論0 收藏0
  • Java線程進階(三七)—— J.U.Ccollections框架:LinkedBlocking

    摘要:接口截止目前為止,我們介紹的阻塞隊列都是實現(xiàn)了接口。該類在構造時一般需要指定容量,如果不指定,則最大容量為。另外,由于內(nèi)部通過來保證線程安全,所以的整體實現(xiàn)時比較簡單的。另外,雙端隊列相比普通隊列,主要是多了隊尾出隊元素隊首入隊元素的功能。 showImg(https://segmentfault.com/img/bVbgZ7j?w=770&h=514); 本文首發(fā)于一世流云專欄:ht...

    light 評論0 收藏0
  • Java線程進階(三三)—— J.U.Ccollections框架:LinkedBlocking

    摘要:在章節(jié)中,我們說過,維護了一把全局鎖,無論是出隊還是入隊,都共用這把鎖,這就導致任一時間點只有一個線程能夠執(zhí)行。入隊鎖對應的是條件隊列,出隊鎖對應的是條件隊列,所以每入隊一個元素,應當立即去喚醒可能阻塞的其它入隊線程。 showImg(https://segmentfault.com/img/bVbgCD9?w=1920&h=1080); 本文首發(fā)于一世流云專欄:https://seg...

    W_BinaryTree 評論0 收藏0

發(fā)表評論

0條評論

garfileo

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<