Maison  >  Article  >  Java  >  Programmation simultanée Java : principe de mise en œuvre du conteneur simultané CopyOnWriteArrayList

Programmation simultanée Java : principe de mise en œuvre du conteneur simultané CopyOnWriteArrayList

php是最好的语言
php是最好的语言original
2018-07-30 11:41:051700parcourir

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.

Qu'est-ce qu'un conteneur CopyOnWrite ?

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.

Le principe d'implémentation de CopyOnWriteArrayList

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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/**

     * Appends the specified element to the end of this list.

     *

     * @param e element to be appended to this list

     * @return <tt>true</tt> (as specified by {@link Collection#add})

     */

    public boolean add(E e) {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        Object[] elements = getArray();

        int len = elements.length;

        Object[] newElements = Arrays.copyOf(elements, len + 1);

        newElements[len] = e;

        setArray(newElements);

        return true;

    finally {

        lock.unlock();

    }

    }

1

2

1

2

3

public E get(int index) {

    return get(getArray(), index);

}

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/**      * 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(); } }
Il n'est pas nécessaire de verrouiller lors de la lecture. Si plusieurs threads ajoutent des données à CopyOnWriteArrayList lors de la lecture, la lecture lira toujours les anciennes données, car lors de l'écriture de l'ancienne CopyOnWriteArrayList. ne sera pas verrouillé.
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 :

<. td>

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

import java.util.Collection;

import java.util.Map;

import java.util.Set;

 

public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {

    private volatile Map<K, V> internalMap;

 

    public CopyOnWriteMap() {

        internalMap = new HashMap<K, V>();

    }

 

    public V put(K key, V value) {

 

        synchronized (this) {

            Map<K, V> newMap = new HashMap<K, V>(internalMap);

            V val = newMap.put(key, value);

            internalMap = newMap;

            return val;

        }

    }

 

    public V get(Object key) {

        return internalMap.get(key);

    }

 

    public void putAll(Map<? extends K, ? extends V> newData) {

        synchronized (this) {

            Map<K, V> newMap = new HashMap<K, V>(internalMap);

            newMap.putAll(newData);

            internalMap = newMap;

        }

    }

}

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.

Scénarios d'application de CopyOnWrite

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 :

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

34

35

package com.ifeve.book;

 

import java.util.Map;

 

import com.ifeve.book.forkjoin.CopyOnWriteMap;

 

/**

 * 黑名单服务

 *

 * @author fangtengfei

 *

 */

public class BlackListServiceImpl {

 

    private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(

            1000);

 

    public static boolean isBlackList(String id) {

        return blackListMap.get(id) == null false true;

    }

 

    public static void addBlackList(String id) {

        blackListMap.put(id, Boolean.TRUE);

    }

 

    /**

     * 批量添加黑名单

     *

     * @param ids

     */

    public static void addBlackList(Map<String,Boolean> ids) {

        blackListMap.putAll(ids);

    }

 

}

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 34 35
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> blackListMap.putAll(ids); } }

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.

Inconvénients de CopyOnWrite

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn