亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

首頁 慕課教程 Java并發工具 Java并發工具 寫時復制的CopyOnWriteArrayList

寫時復制的 CopyOnWriteArrayList

1. 前言

本節帶領大家認識第二個常用的 Java 并發容器類之 CopyOnWriteArrayList。

本節先介紹 CopyOnWriteArrayList 工具類表達的概念和最基本用法,接著通過一個生活中的例子為大家解釋 CopyOnWriteArrayList 工具類的使用場合,然后通過簡單的編碼實現此場景。

下面我們正式開始介紹吧。

2. 概念解釋

什么是 CopyOnWrite ? 顧名思義,就是 “寫數據的時候先拷貝一份副本,在副本上寫數據”。為什么需要在寫的時候以這種方式執行呢?當然是為了提高效率。

當多個線程同時操作一個 ArrayList 對象時,為了線程安全需要對操作增加線程安全相關的鎖控制。采用 CopyOnWrite 方式,可以做到讀操作不用加鎖,而只對寫操作加鎖,且可以很方便地反饋寫后的結果給到讀操作。CopyOnWriteArrayList 就是采用這種優化思想,對 ArrayList 做的線程安全特性增強。我們通過一張圖了解其基本原理。
圖片描述
概念已經了解了,CopyOnWriteArrayList 工具類最基本的用法是怎樣的呢?看下面。

3. 基本用法

此工具類和 ArrayList 在使用方式方面很類似。

// 創建一個 CopyOnWriteArrayList 對象
CopyOnWriteArrayList phaser = new CopyOnWriteArrayList();
// 新增
copyOnWriteArrayList.add(1);
// 設置(指定下標)
copyOnWriteArrayList.set(0, 2);
// 獲取(查詢)
copyOnWriteArrayList.get(0);
// 刪除
copyOnWriteArrayList.remove(0);
// 清空
copyOnWriteArrayList.clear();
// 是否為空
copyOnWriteArrayList.isEmpty();
// 是否包含
copyOnWriteArrayList.contains(1);
// 獲取元素個數
copyOnWriteArrayList.size();

是不是很簡單,那 CopyOnWriteArrayList 應用在哪些場合比較合適呢?下面我們給出最常用的場景說明。

4. 常用場景

CopyOnWriteArrayList 并發容器用于讀多寫少的并發場景。因為采用了寫時復制的實現原理,當存在大量寫的時候,內存中會頻繁復制原有數據的副本,如果原有數據集很大,則很容易造成內存飆升甚至內存異常。在日常研發中,可用于靜態數據字典的緩存場合,如黑白名單過濾判定。

注意,CopyOnWriteArrayList 不能保證寫入的數據實時讀取到,只保證數據的最終一致。是因為寫入時需要復制一份原有內容,以及寫入后的新老內容互換都需要一定時間。

我們舉一個 IP 黑名單判定的例子:當應用接入外部請求后,為了防范風險,一般會對請求做一些特征判定,如對請求 IP 是否合法的判定就是一種。IP 黑名單偶爾會被系統運維人員做更新。我們使用 CopyOnWriteArrayList 工具類實現此場景,請看下面代碼。

5. 場景案例

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListTest {

    // 創建一個 CountDownLatch 對象,代表黑名單列表
    private static CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
    // 模擬初始化的黑名單數據
    static {
        copyOnWriteArrayList.add("ipAddr0");
        copyOnWriteArrayList.add("ipAddr1");
        copyOnWriteArrayList.add("ipAddr2");
    }

    // 主線程
    public static void main(String[] args) throws InterruptedException {
        Runnable task = new Runnable() {
            public void run() {
                // 模擬接入用時
                try {
                    Thread.sleep(new Random().nextInt(5000));
                } catch (Exception e) {}

                String currentIP = "ipAddr" + new Random().nextInt(5);
                if (copyOnWriteArrayList.contains(currentIP)) {
                    System.out.println(Thread.currentThread().getName() + " IP " + currentIP + "命中黑名單,拒絕接入處理");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + " IP " + currentIP + "接入處理...");
            }
        };
        new Thread(task, "請求1").start();
        new Thread(task, "請求2").start();
        new Thread(task, "請求3").start();

        Runnable updateTask = new Runnable() {
            public void run() {
                // 模擬用時
                try {
                    Thread.sleep(new Random().nextInt(2000));
                } catch (Exception e) {}

                String newBlackIP = "ipAddr3";
                copyOnWriteArrayList.add(newBlackIP);
                System.out.println(Thread.currentThread().getName() + " 添加了新的非法IP " + newBlackIP);
            }
        };
        new Thread(updateTask, "IP黑名單更新").start();

        Thread.sleep(1000000);
    }
}

運行上面代碼,我們觀察一下運行結果。

請求2 IP ipAddr1命中黑名單,拒絕接入處理
IP黑名單更新 添加了新的非法IP ipAddr3
請求3 IP ipAddr3命中黑名單,拒絕接入處理
請求1 IP ipAddr4接入處理...

觀察結果,和我們的預期一致。

6. 小結

本節通過一個簡單的例子,介紹了 CopyOnWriteArrayList 的使用場景和基本用法。希望大家在學習過程中,多思考勤練習,早日掌握之。