CopyOnWriteArrayList真的完全線程安全嗎

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

CopyOnWriteArrayList原理

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

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

而因?yàn)閷懖僮鞯臅r(shí)候不會(huì)對當(dāng)前容器做任何處理,所以我們可以對容器進(jìn)行并發(fā)的讀,而不需要加鎖,也就是讀寫分離。

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

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

數(shù)組越界

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

測試一下,首先向CopyOnWriteArrayList里面塞10000個(gè)測試數(shù)據(jù),啟動(dòng)兩個(gè)線程,一個(gè)不斷的刪除元素,一個(gè)不斷的讀取容器中最后一個(gè)數(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();
    }

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


error.png

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

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

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

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