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.synchronizedList
contenant 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
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)
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:
- Verrouiller
- Selon l'endroit où l'index est supprimé, faites des copies de stratégie différentes
- 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.