Copy-On-Write, appelé COW, est une stratégie d'optimisation utilisée en programmation. L'idée de base est que tout le monde partage le même contenu depuis le début. Lorsque quelqu'un souhaite modifier le contenu, il le copie pour former un nouveau contenu, puis le modifie. C'est une sorte de stratégie de paresse différée. À partir du JDK 1.5, le package de concurrence Java fournit deux conteneurs simultanés implémentés à l'aide du mécanisme CopyOnWrite, à savoir CopyOnWriteArrayList et CopyOnWriteArraySet. Le conteneur CopyOnWrite est très utile et peut être utilisé dans de nombreux scénarios simultanés.
Un conteneur CopyOnWrite est un conteneur qui est copié lors de l'écriture. La compréhension populaire est que lorsque nous ajoutons des éléments à un conteneur, nous ne les ajoutons pas directement au conteneur actuel, mais copions d'abord le conteneur actuel pour créer un nouveau conteneur, puis ajoutons des éléments au nouveau conteneur. Pointez ensuite la référence du conteneur d'origine vers le nouveau conteneur. L'avantage de ceci est que nous pouvons effectuer des lectures simultanées sur le conteneur CopyOnWrite sans verrouillage, car le conteneur actuel n'ajoutera aucun élément. Par conséquent, le conteneur CopyOnWrite est également une idée de séparation de la lecture et de l'écriture, et la lecture et l'écriture sont des conteneurs différents.
Avant d'utiliser CopyOnWriteArrayList, lisons son code source pour comprendre comment il est implémenté. Le code suivant est l'implémentation de la méthode add dans CopyOnWriteArrayList (ajout d'éléments à CopyOnWriteArrayList). Vous pouvez constater que vous devez verrouiller lors de l'ajout, sinon N copies seront copiées lors de l'écriture avec plusieurs threads.
2
|
/**
* Ajoute l'élément spécifié à la fin de cette liste.
*
* @param e élément à ajouter à cette liste
* @return <tt>true</tt> (comme spécifié par {@link Collection#add})
*/
public booléen add(E e) {
final Verrou ReentrantLock = this .lock;
lock.lock();
essayez {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1 );
newElements[len] = e;
setArray(newElements);
retour vrai ;
} enfin {
lock.unlock();
}
}
|
1 2 3 |
public E get( int index) {
return get(getArray(), index);
}
|
Il n'y a pas de CopyOnWriteMap fourni dans le JDK. On peut se référer à CopyOnWriteArrayList pour en implémenter un. Le code de base est le suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
importer
java.util.Collection;
importer
java.util.Map;
importer
java.util.Set;
public
class
CopyOnWriteMap<K, V>
impléments
Map<K, V>, Clonable {
privé
volatile
Carte<K, V> internalMap;
public
CopyOnWriteMap() {
internalMap =
nouveau
HashMap<K, V>();
}
public
V put (clé K, valeur V) {
synchronisé
(
ce
) {
Carte<K, V> newMap =
new
HashMap<K, V>(internalMap);
V val = newMap.put(clé, valeur);
internalMap = newMap;
retour
val;
}
}
public
V get(Clé d'objet) {
retour
internalMap.get(key);
}
public
void
putAll(Map<?
étend
K , ?
étend
V> newData) {
synchronisé
(
ce
) {
Carte<K, V> newMap =
new
HashMap<K, V>(internalMap);
newMap.putAll(newData);
internalMap = newMap;
}
}
}
L'implémentation est très simple. Tant que nous comprenons le mécanisme CopyOnWrite, nous pouvons implémenter divers conteneurs CopyOnWrite et les utiliser dans différents scénarios d'application.
Le conteneur simultané CopyOnWrite est utilisé dans des scénarios simultanés avec plus de lecture et moins d'écriture. Par exemple, des scénarios d'accès et de mise à jour de liste blanche, de liste noire et de catégorie de produits. Si nous avons un site Web de recherche, l'utilisateur saisit des mots-clés pour rechercher du contenu dans la zone de recherche de ce site Web, mais la recherche de certains mots-clés n'est pas autorisée. Ces mots-clés non recherchables seront placés dans une liste noire, mise à jour chaque nuit. Lorsque l'utilisateur effectue une recherche, il vérifiera si le mot-clé actuel est dans la liste noire. Si tel est le cas, il indiquera que la recherche ne peut pas être effectuée. Le code d'implémentation est le suivant :
|
package com.ifeve.book;
importer java.util.Map;
importer com.ifeve.book.forkjoin.CopyOnWriteMap;
/**
* 黑名单服务
*
* @auteur fangtengfei
*
*/
public class BlackListServiceImpl {
privé statique CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(
1000 );
public static boolean isBlackList(String id) {
retour blackListMap.get(id) == null faux : vrai ;
}
public static void addBlackList(String id) {
blackListMap.put(id, Boolean.TRUE);
}
/**
* Ajouter une liste noire par lots
*
* @identifiants param
*/
public static void addBlackList(Map<String,Boolean> ids) { code>
|
Le code est très simple, mais il y a deux choses auxquelles vous devez faire attention lorsque vous utilisez CopyOnWriteMap :
1. Réduisez la surcharge d'expansion. Initialisez la taille de CopyOnWriteMap en fonction des besoins réels pour éviter la surcharge de l'expansion de CopyOnWriteMap lors de l'écriture.
2. Utilisez l'ajout par lots. Parce que chaque fois que vous ajoutez, le conteneur sera copié à chaque fois, donc réduire le nombre d'ajouts peut réduire le nombre de fois que le conteneur est copié. Par exemple, utilisez la méthode addBlackList dans le code ci-dessus.
Le conteneur CopyOnWrite présente de nombreux avantages, mais il existe également deux problèmes, à savoir l'utilisation de la mémoire et la cohérence des données. Vous devez donc y prêter attention lors du développement.
Problème d'utilisation de la mémoire. En raison du mécanisme de copie sur écriture de CopyOnWrite, lorsqu'une opération d'écriture est effectuée, deux objets résideront simultanément dans la mémoire, l'ancien objet et le nouvel objet écrit (remarque : lors de la copie, seules les références dans le conteneur sont copiés. Uniquement lors de l'écriture, de nouveaux objets seront créés et ajoutés au nouveau conteneur, tandis que les objets de l'ancien conteneur sont toujours utilisés, il y a donc deux copies de la mémoire des objets). Si ces objets occupent une quantité de mémoire relativement importante, par exemple environ 200 Mo, alors l'écriture de 100 Mo supplémentaires de données occupera 300 Mo de mémoire, ce qui peut entraîner des Yong GC et des Full GC fréquents. Auparavant, nous utilisions un service dans notre système qui utilisait le mécanisme CopyOnWrite pour mettre à jour les objets volumineux chaque nuit, ce qui entraînait un GC complet de 15 secondes chaque nuit, et le temps de réponse de l'application devenait également plus long.
Compte tenu du problème d'utilisation de la mémoire, vous pouvez réduire la consommation de mémoire des objets volumineux en compressant les éléments dans le conteneur. Par exemple, si les éléments sont tous des nombres décimaux, vous pouvez envisager de les compresser en hexadécimal ou en nombres décimaux. Nombres hexadécimaux Base 64. Ou n'utilisez pas le conteneur CopyOnWrite, mais utilisez d'autres conteneurs simultanés, tels que ConcurrentHashMap.
Problème de cohérence des données. Le conteneur CopyOnWrite ne peut garantir que la cohérence finale des données, mais ne peut garantir la cohérence en temps réel des données. Donc, si vous souhaitez que les données écrites soient lues immédiatement, veuillez ne pas utiliser le conteneur CopyOnWrite.
Articles connexes :
Programmation simultanée Java : CountDownLatch, CyclicBarrier et Semaphore
[Pratique de programmation simultanée JAVA] Séquence de verrouillage Deadlock
Vidéos associées :
Tutoriel vidéo sur les applications avancées de la bibliothèque multithread et simultanée Java
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!