溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java的CopyOnWrite怎么實現(xiàn)

發(fā)布時間:2022-02-23 16:11:48 來源:億速云 閱讀:131 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹了Java的CopyOnWrite怎么實現(xiàn)的相關(guān)知識,內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Java的CopyOnWrite怎么實現(xiàn)文章都會有所收獲,下面我們一起來看看吧。

概念

CopyOnWrite 是什么呢,從字面上看,就是在寫入時復(fù)制??雌饋砻菜坪芎唵?,那么寫入時復(fù)制,具體是怎么實現(xiàn)的呢?

先來說說思想,具體怎么實現(xiàn)等下分析

CopyOnWrite 的思想就是:當(dāng)向一個容器中添加元素的時候,不是直接在當(dāng)前這個容器里面添加的,而是復(fù)制出來一個新的容器,在新的容器里面添加元素,添加完畢之后再將原容器的引用指向新的容器,這樣就實現(xiàn)了寫入時復(fù)制

你還記得在提到數(shù)據(jù)庫的時候,一般都會說主從復(fù)制,讀寫分離嗎?CopyOnWrite 的設(shè)計思想是不是和經(jīng)常說的主從復(fù)制,讀寫分離如出一撤?

優(yōu)缺點

了解概念之后,對它的優(yōu)缺點應(yīng)該就比較好理解了

優(yōu)點就是,讀和寫可以并行執(zhí)行,因為讀的是原來的容器,寫的是新的容器,它們之間互不影響,所以讀和寫是可以并行執(zhí)行的,在某些高并發(fā)場景下,可以提高程序的響應(yīng)時間

但是呢,你也看到了, CopyOnWrite 是在寫入的時候,復(fù)制了一個新的容器出來,所以要考慮它的內(nèi)存開銷問題,又回到了在學(xué)算法時一直強(qiáng)調(diào)的一個思想:拿空間換時間

需要注意一下,它只保證數(shù)據(jù)的最終一致性。因為在讀的時候,讀取的內(nèi)容是原容器里面的內(nèi)容,新添加的內(nèi)容是讀取不到的

基于它的優(yōu)缺點應(yīng)該就可以得出一個結(jié)論:CopyOnWrite 適用于寫操作非常少的場景,而且還能夠容忍讀寫的暫時不一致 如果你的應(yīng)用場景不適合,那還是考慮使用別的方法來實現(xiàn)吧

還有一點需要注意的是:在寫入時,它會復(fù)制一個新的容器,所以如果有寫入需求的話,最好可以批量寫入,因為每次寫入的時候,容器都會進(jìn)行復(fù)制,如果能夠減少寫入的次數(shù),就可以減少容器的復(fù)制次數(shù)

JUC 包下,實現(xiàn) CopyOnWrite 思想的就是 CopyOnWriteArrayList & CopyOnWriteArraySet 這兩個方法,本篇文章側(cè)重于講清楚 CopyOnWriteArrayList

CopyOnWriteArrayList

CopyOnWriteArrayList 中,需要注意的是 add 方法:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 在寫入的時候,需要加鎖,如果不加鎖的話,在多線程場景下可能會被 copy 出 n 個副本出來
        // 加鎖之后,就能保證在進(jìn)行寫時,只有一個線程在操作
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 復(fù)制原來的數(shù)組
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 將要添加的元素添加到新數(shù)組中
            newElements[len] = e;
            // 將對原數(shù)組的引用指向新的數(shù)組
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

在寫的時候需要加鎖,但是在讀取的時候不需要添加

因為讀取的是原數(shù)組的元素,對新數(shù)組沒有什么影響,加了鎖反而會增加性能開銷

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

舉個例子:

CopyOnWriteJUC 包下,那么它就保證了線程安全

咱們來做個小 demo 驗證一下:

@Slf4j
public class ArrayListExample {


    // 請求總數(shù)
    public static int clientTotal = 5000;


    // 同時并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;


    private static List<Integer> list = new ArrayList<>();


    public static void  main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}",list.size());
    }
    private static void update(int i){
        list.add(i);
    }
}

上面是客戶端請求 5000 次,有 200 個線程在同時請求,我使用的是 ArrayList 實現(xiàn),咱們看下打印結(jié)果:

Java的CopyOnWrite怎么實現(xiàn)

如果是線程安全的話,那么最后的結(jié)果應(yīng)該是 5000 才對,多運行幾次你會發(fā)現(xiàn),每次程序的執(zhí)行結(jié)果都是不一樣的

如果是 CopyOnWriteArrayList 呢?

@Slf4j
public class CopyOnWriteArrayListExample {


    // 請求總數(shù)
    public static int clientTotal = 5000;


    // 同時并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;


    private static List<Integer> list = new CopyOnWriteArrayList<>();


    public static void  main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("excepiton",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}",list.size());
    }
    private static void update(int i){
        list.add(i);
    }
}

關(guān)于“Java的CopyOnWrite怎么實現(xiàn)”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“Java的CopyOnWrite怎么實現(xiàn)”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI