CopyOnWriteArrayList真的完全線程安全嗎

CopyOnWriteArrayList是開發(fā)過程中常用的一種并發(fā)容器,多用于讀多寫少的并發(fā)場景。但是CopyOnWriteArrayList真的能做到完全的線程安全嗎?
答案是并不能。

CopyOnWriteArrayList原理

我們可以看出當(dāng)我們向容器添加或刪除元素的時候,不直接往當(dāng)前容器添加刪除,而是先將當(dāng)前容器進行Copy,復(fù)制出一個新的容器,然后新的容器里添加刪除元素,添加刪除完元素之后,再將原容器的引用指向新的容器,整個過程加鎖,保證了寫的線程安全。

    public boolean add(E e) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }

    public E remove(int index) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                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;
        }
    }

而因為寫操作的時候不會對當(dāng)前容器做任何處理,所以我們可以對容器進行并發(fā)的讀,而不需要加鎖,也就是讀寫分離。

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

一般來講我們使用時,會用一個線程向容器中添加元素,一個線程來讀取元素,而讀取的操作往往更加頻繁。寫操作加鎖保證了線程安全,讀寫分離保證了讀操作的效率,簡直完美。

數(shù)組越界

但想象一下如果這時候有第三個線程進行刪除元素操作,讀線程去讀取容器中最后一個元素,讀之前的時候容器大小為i,當(dāng)去讀的時候刪除線程突然刪除了一個元素,這個時候容器大小變?yōu)榱薸-1,讀線程仍然去讀取第i個元素,這時候就會發(fā)生數(shù)組越界。

測試一下,首先向CopyOnWriteArrayList里面塞10000個測試數(shù)據(jù),啟動兩個線程,一個不斷的刪除元素,一個不斷的讀取容器中最后一個數(shù)據(jù)。

    public void test(){
        for(int i = 0; i<10000; i++){
            list.add("string" + i);
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (list.size() > 0) {
                        String content = list.get(list.size() - 1);
                    }else {
                        break;
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if(list.size() <= 0){
                        break;
                    }
                    list.remove(0);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

運行,可以看出刪除到第7個元素的時候就發(fā)生了數(shù)組越界


error.png

從上可以看出CopyOnWriteArrayList并不是完全意義上的線程安全,如果涉及到remove操作,一定要謹(jǐn)慎處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容