天天看點

【并發容器精講二、】CopyOnWriteArrayList1. 誕生的曆史和原因2. 适用場景3. 讀寫規則4. 實作原理&源碼分析5. 缺點

@[toc]

1. 誕生的曆史和原因

  • 代替Vector和SyschronizedList 就像 ConcurrentHashMap 代替了SyschronizedMap的原因一樣
  • Vector和SyschronizedList 鎖的粒度太大,并發效率較低,并且疊代時無法編輯
  • Copy-On-Write 并發容器還包括CopyOnWriteArraySet,用來代替同步 Set

2. 适用場景

  • 讀操作盡可能的快一些,而寫即使慢一些也沒關系

3. 讀寫規則

  • 回顧讀寫鎖:讀讀共享、其他都互斥,(寫寫互斥,讀寫互斥,寫讀互斥)
  • 讀寫鎖規則的更新:讀取是完全不用加鎖的,并且更厲害的是寫入也不會阻塞讀取操作,隻有寫入寫入才會同步等待
  • ArrayList
public static void main(String[] args) {
        List list=new ArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(list);
            Object next = iterator.next();
            System.out.println(next);
            if(next.equals("11")){
                list.remove("22");
            }
            if(next.equals("33")){
                list.add("2222");
            }
        }
}           
【并發容器精講二、】CopyOnWriteArrayList1. 誕生的曆史和原因2. 适用場景3. 讀寫規則4. 實作原理&源碼分析5. 缺點

CopyOnWriteArrayList

public static void main(String[] args) {
        CopyOnWriteArrayList list = new CopyOnWriteArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(list);
            Object next = iterator.next();
            System.out.println(next);
            if (next.equals("11")) {
                list.remove("22");
            }
            if (next.equals("33")) {
                list.add("2222");
            }
        }


    }           
【并發容器精講二、】CopyOnWriteArrayList1. 誕生的曆史和原因2. 适用場景3. 讀寫規則4. 實作原理&源碼分析5. 缺點

通過案例我們可以發現 ArrayList 疊代修改的時候會 抛出異常 ,而CopyOnWriteArrayList 不會

4. 實作原理&源碼分析

CopyOnWrite的含義

建立新副本、讀寫分離

不可變原理

疊代的時候

我們看下ArrayList 源碼為什麼會報錯

@SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }           

點開方法,他會進行一個比較操作是以會出現異常

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }           

我們看下 CopyOnWriteArrayList 源碼

這是CopyOnWriteArrayList存放資料的地方,隻能通過getArray擷取

并且他會上鎖,用的ReentrantLock

/** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();
    
 /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;           

初始化 :構造函數 建立個空的數組

/**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }           

add 方法

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //先拿到鎖
        lock.lock();
        try {
        //擷取初始化數組
            Object[] elements = getArray();
            //擷取長度 //copy出一個新的數組
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //将我們的參數添加到這個位置
            newElements[len] = e;
            //set方法添加至
            setArray(newElements);
            return true;
        } finally {
        //釋放鎖
            lock.unlock();
        }
    }           

get方法

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

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }           
public boolean hasNext() {
            return cursor < snapshot.length;
        }

        public boolean hasPrevious() {
            return cursor > 0;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        //判斷 true 和  false 
        //邏輯簡單了許多
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }           

5. 缺點

  • 資料一緻性問題 :CopyOnWrite隻能保證資料最終一緻性的問題,不能保證資料實時一緻性,是以希望寫入的資料馬上能讀到,不要使用CopyOnWrite
  • 記憶體占用問題:因為CopyOnWrite的寫是複制機制,是以在寫的操作,是以記憶體中會存在2個對象

個人部落格位址:

http://blog.yanxiaolong.cn/

繼續閱讀