【線程安全的List】CopyOnWriteArrayList的原理及使用

1、原理

  • CopyOnWriteArrayList是一個線程安全的ArrayList

  • 如果一段併發程序,讀操作明顯多於寫操作的話,那麼使用CopyOnWriteArrayList的性能會比Vector更高

  • CopyOnWriteArrayList的實現原理就是讀寫分離,它對所有的寫操作都使用ReentrantLock來加鎖,對所有的讀操作都不加鎖,那它是怎麼保證線程安全性問題的呢?

  • CopyOnWriteArrayList在寫操作的時候,都會將list中的數組copy一份作爲緩存,然後對該緩存中的數組進行操作(此時若有其他線程過來讀的話,那麼該線程讀的還是原先沒有被修改過的數組,若有其他線程過來寫的話,那麼該線程會因爲ReentrantLock的原因被鎖在外面。),操作完畢後再將list中的數組地址引用指向修改後的新數組地址。

  • 由CopyOnWriteArrayList的原理我們可以看出,我們每次往list裏面寫數據的時候,數組都需要重新copy一份,所以CopyOnWriteArrayList不需要實現像ArrayList一樣的擴容機制,初始創建時讓list中的數組長度爲0,我們每次add元素的時候,只需要對新數組長度進行加1操作即可,所以CopyOnWriteArrayList實現起來相對還是比ArrayList簡單的。

    // 構造方法
    public CopyOnWriteArrayList() {
    setArray(new Object[0]);
    }
    
    // 添加一個元素
    public boolean add(E e) {
        
        // 加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        
            // 創建一個新數組,長度+1
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            
            // 將元素添加到新數組的末端,並將新數組賦值給本對象中的array
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            // 解鎖
            lock.unlock();
        }
    }
    
    // 獲取一個元素,沒加鎖
    public E get(int index) {
        return get(getArray(), index);
    }
    
  • 所以我們可以看出,如果是讀操作十分頻繁的話,那麼多線程下使用CopyOnWriteArrayList的性能基本上跟ArrayList差不多了。但如果是寫操作十分頻繁的話,建議還是不要使用CopyOnWriteArrayList了,因爲它會造成數組的不斷擴容及複製,十分耗性能。這其實就跟我們數據庫讀寫分離的原理是一樣的,如果寫操作很多的話,那麼主從庫就會不斷的執行復制操作,消耗性能。但如果是讀操作多的話,由於該庫只用於讀,所以不會發生數據庫事務鎖,效率就會比一般的單庫查詢快很多。

2、使用

  • CopyOnWriteArrayList的使用和ArrayList差不多,這裏沒什麼好說的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章