Analyse du code source principal de CopyOnWriteArrayList

Titre de l'image

J'ai continué à courir juste pour me rattraper, qui avait de grands espoirs. -Livingston

0 Préface

Nous savons que ArrayList non-thread-safe, vous devez posséder ou d' utiliser un verrou Collections.synchronizedListcontenant emballage fournit des mécanismes de thread-safe pour obtenir accès concurrentiel de l' utilisation JDK1.5 début UCC copy-on-write dans la liste -. CopyOnWriteArrayList, appelé COW

Titre de l'image

1 idées de conception CopyOnWrite

1.1 Concepts de base

CopyOnWrite copy-on-write. De manière générale, lorsque nous ajoutons des éléments à un conteneur, nous ne les ajoutons pas directement au conteneur actuel, mais copions d'abord le conteneur actuel dans un nouveau conteneur, ajoutez des éléments au nouveau conteneur, ajoutez des éléments Ensuite, pointez le conteneur d'origine vers le nouveau conteneur. Autrement dit, tout le monde partage le même contenu au début. Lorsque quelqu'un souhaite modifier le contenu, il copie le contenu pour former un nouveau contenu, puis le modifie. Retarder la stratégie paresseuse.

1.2 Avantages de conception

Le conteneur CopyOnWrite peut être lu simultanément sans verrouillage, car le conteneur actuel n'ajoutera aucun élément. Il s'agit donc également d'une idée de lecture et d'écriture distincte, lisez et écrivez différents conteneurs.

2 Système d'héritage

  • Similaire au système d'héritage d'ArrayList
    Titre de l'image
    Titre de l'image

3 Propriétés

  • Verrous pour protéger tous les modificateurs
    Titre de l'image
  • Tableaux accessibles uniquement via getArray / setArray
    Titre de l'image
  • verrouiller l'offset de la mémoire
    Titre de l'image

4 Méthode de construction

4.1 Aucun paramètre

  • Créer une liste vide
    Titre de l'image

4.2 Participation

  • Créez une liste qui contient les éléments de la collection spécifiée, dont l'ordre est renvoyé par l'itérateur de la collection.
    Titre de l'image
  • Créer une liste contenant une copie du tableau donné
    Titre de l'image

Commençons par regarder le code source et comment le copier en écriture.

5 ajouter (E et)

L'ajout d'éléments dans le COW nécessite un verrouillage, sinon N copies seront copiées lors de l'écriture simultanée!

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 1.加锁
    lock.lock();
    try {
        // 得到原数组
        Object[] elements = getArray();
        int len = elements.length;
        // 2.复制出新数组,加一是因为要添加yi'ge'yuan's
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 把新元素添加到新数组里,直接放在数组尾部
        newElements[len] = e;
        // 把原数组引用指向新数组
        setArray(newElements);
        return true;
    } finally {
        // finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁 
        lock.unlock();
    }
}
复制代码

getArray

  • Obtenez un tableau. Non-priavte, de sorte qu'il soit également accessible à partir de la classe CopyOnWriteArraySet (qui combine directement CopyOnWriteArrayList en tant que variable membre)
    Titre de l'image

setArray

  • Définir une référence à un nouveau tableau
    Titre de l'image

Les deux sont verrouillés, pourquoi avez-vous besoin de copier le tableau sans modifier directement le tableau d'origine?

  • volatile est modifié par une référence de tableau! Modifiez simplement la valeur de quelques éléments dans le tableau d'origine. Cette opération ne peut pas être utilisée pour la visibilité. Vous devez modifier l'adresse mémoire du tableau
  • L'exécution de copyOf sur le nouveau tableau n'a aucun effet sur le tableau d'origine. Ce n'est qu'après que le nouveau tableau est complètement copié qu'il peut être accédé en externe, en évitant les effets négatifs possibles des changements de données dans le tableau d'origine

6 get

get (int index)

  • Lire l'élément de position spécifié
    Titre de l'image

get (Object [] a, int index)

Titre de l'image

Il n'est pas nécessaire d'ajouter un verrou lors de la lecture. Si d'autres threads ajoutent des données à ArrayList pendant la lecture, la lecture ne lira toujours que les anciennes données, car l'ancien tableau ne sera pas verrouillé lors de l'écriture.

7 supprimer

7.1 Supprimer l'index spécifié

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        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 {
            // 若删除的数据在数组中间:
            // 1. 设置新数组的长度减一,因为是减少一个元素
            // 2. 从 0 拷贝到数组新位置
            // 3. 从新位置拷贝到数组尾部
            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;
    } finally {
        lock.unlock();
    }
}
复制代码

Encore trois axes:

  1. Verrouiller
  2. Selon l'endroit où l'index est supprimé, faites des copies de stratégie différentes
  3. Débloquer

7.2 Suppression en masse

public boolean removeAll(Collection<?> c) {
    if (c == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        if (len != 0) {
            // newlen 表新数组的索引位置,新数组中存在不包含在 c 中的元素
            int newlen = 0;
            Object[] temp = new Object[len];
            // 循环,把不包含在 c 里面的元素,放到新数组中
            for (int i = 0; i < len; ++i) {
                Object element = elements[i];
                // 不包含在 c 中的元素,从 0 开始放到新数组中
                if (!c.contains(element))
                    temp[newlen++] = element;
            }
            // 拷贝新数组,变相的删除了不包含在 c 中的元素
            if (newlen != len) {
                setArray(Arrays.copyOf(temp, newlen));
                return true;
            }
        }
        return false;
    } finally {
        lock.unlock();
    }
}
复制代码

Au lieu de supprimer directement les éléments du tableau un par un, évaluez d'abord les valeurs du tableau dans une boucle, placez les données qui n'ont pas besoin d'être supprimées dans le tableau temporaire, et enfin les données du tableau temporaire sont les données que nous n'avons pas besoin de supprimer.

8 Résumé

Le conteneur de simultanéité CopyOnWrite convient aux scénarios simultanés avec plus de lectures et moins d'écritures. Le conteneur CopyOnWrite présente de nombreux avantages, mais il y a aussi des problèmes. Vous devez faire attention lors du développement:

Problèmes d'utilisation de la mémoire

Lors de l'écriture, la mémoire de deux objets résidera dans la mémoire en même temps, l'ancien objet et le nouvel objet écrit (seule la référence dans le conteneur est copiée lors de la copie, mais le nouvel objet est créé et ajouté au nouveau conteneur lors de l'écriture, et Les objets du conteneur sont toujours utilisés, il y a donc deux copies de la mémoire des objets. Si ces objets occupent une grande quantité de mémoire, cela risque de provoquer des GC fréquents et le temps de réponse de l'application s'allonge. Mémoire, ou n'utilisez pas directement le conteneur CopyOnWrite, mais utilisez d'autres conteneurs simultanés, tels que ConcurrentHashMap.

Problèmes de cohérence des données

Le conteneur CopyOnWrite ne peut garantir que des données, 最终一致性mais pas des données 实时一致性, veuillez l'utiliser comme il convient.

Je suppose que tu aimes

Origine juejin.im/post/5e93281d51882573716a9c8b
conseillé
Classement