Une introduction aux collections Java synchronisées

Introduction aux collections Java synchronisées

1. Vue d'ensemble

Lecollections framework est un composant clé de Java. Il fournit un grand nombre d'interfaces et d'implémentations, ce qui nous permet de créer et de manipuler différents types de collections de manière simple.

Bien que l'utilisation de collections non synchronisées en clair soit simple dans l'ensemble, elle peut également devenir un processus fastidieux et sujet aux erreurs lorsque vous travaillez dans des environnements multithreads (a.k.a. programmation concurrente).

Par conséquent, la plate-forme Java fournit un support solide pour ce scénario grâce à différentes synchronisationswrappers implémentées dans la classeCollections.

Ces wrappers facilitent la création de vues synchronisées des collections fournies à l'aide de plusieurs méthodes d'usine statique.

Dans ce didacticiel,we’ll take a deep dive into these *static synchronization wrappers. Also, we’ll highlight the difference between synchronized collections and concurrent collections. *

2. La méthodesynchronizedCollection()

Le premier wrapper de synchronisation que nous aborderons dans ce tour d'horizon est la méthodesynchronizedCollection(). Comme son nom l'indique,it returns a thread-safe collection backed up by the specified Collection.

Maintenant, pour comprendre plus clairement comment utiliser cette méthode, créons un test unitaire de base:

Collection syncCollection = Collections.synchronizedCollection(new ArrayList<>());
    Runnable listOperations = () -> {
        syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
    };

    Thread thread1 = new Thread(listOperations);
    Thread thread2 = new Thread(listOperations);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();

    assertThat(syncCollection.size()).isEqualTo(12);
}

Comme indiqué ci-dessus, la création d'une vue synchronisée de la collection fournie avec cette méthode est très simple.

Pour démontrer que la méthode retourne réellement une collection thread-safe, nous créons d’abord deux ou trois threads.

Ensuite, nous injectons une instanceRunnable dans leurs constructeurs, sous la forme d'une expression lambda. Gardons à l'esprit queRunnable est une interface fonctionnelle, nous pouvons donc la remplacer par une expression lambda.

Enfin, nous vérifions simplement que chaque thread ajoute effectivement six éléments à la collection synchronisée, de sorte que sa taille finale est de douze.

3. La méthodesynchronizedList()

De même, comme pour la méthodesynchronizedCollection(), nous pouvons utiliser le wrappersynchronizedList() pour créer unList synchronisé.

Comme on pouvait s'y attendre,the method returns a thread-safe view of the specified List:

List syncList = Collections.synchronizedList(new ArrayList<>());

Sans surprise, l'utilisation de la méthodesynchronizedList() semble presque identique à son homologue de niveau supérieur,synchronizedCollection().

Par conséquent, comme nous venons de le faire dans le test unitaire précédent, une fois que nous avons créé unList synchronisé, nous pouvons générer plusieurs threads. Après cela, nous les utiliserons pour accéder / manipuler lesListcibles de manière thread-safe.

De plus, si nous voulons effectuer une itération sur une collection synchronisée et éviter des résultats inattendus, nous devons explicitement fournir notre propre implémentation thread-safe de la boucle. Par conséquent, nous pourrions y parvenir en utilisant un blocsynchronized:

List syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List uppercasedCollection = new ArrayList<>();

Runnable listOperations = () -> {
    synchronized (syncCollection) {
        syncCollection.forEach((e) -> {
            uppercasedCollection.add(e.toUpperCase());
        });
    }
};

Dans tous les cas où nous devons parcourir une collection synchronisée, nous devrions implémenter cet idiome. En effet, l'itération sur une collection synchronisée est effectuée via plusieurs appels dans la collection. Par conséquent, elles doivent être effectuées en une seule opération atomique.

The use of the synchronized block ensures the atomicity of the operation.

4. La méthodesynchronizedMap()

La classeCollections implémente un autre wrapper de synchronisation soigné, appelésynchronizedMap().. Nous pourrions l'utiliser pour créer facilement unMap synchronisé.

The method returns a thread-safe view of the supplied Map implementation:

Map syncMap = Collections.synchronizedMap(new HashMap<>());

5. La méthodesynchronizedSortedMap()

Il existe également une implémentation homologue de la méthodesynchronizedMap(). Il s'appellesynchronizedSortedMap(), que nous pouvons utiliser pour créer une instanceSortedMap synchronisée:

Map syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());

6. La méthodesynchronizedSet()

Ensuite, dans cette revue, nous avons la méthodesynchronizedSet(). Comme son nom l'indique, il nous permet de créer desSets synchronisés avec un minimum de tracas.

The wrapper returns a thread-safe collection backed by the specified Set:

Set syncSet = Collections.synchronizedSet(new HashSet<>());

7. La méthodesynchronizedSortedSet()

Enfin, le dernier wrapper de synchronisation que nous présenterons ici estsynchronizedSortedSet().

Semblable à d'autres implémentations de wrapper que nous avons examinées jusqu'à présent,the method returns a thread-safe version of the given SortedSet:

SortedSet syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());

8. Collections synchronisées et simultanées

Jusqu'à présent, nous avons examiné de plus près les wrappers de synchronisation du framework de collections.

Maintenant, concentrons-nous sur the differences between synchronized collections and concurrent collections, comme les implémentations deConcurrentHashMap etBlockingQueue.

8.1. Collections synchronisées

Synchronized collections achieve thread-safety through intrinsihttps://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html[c locking], and the entire collections are locked. Le verrouillage intrinsèque est implémenté via des blocs synchronisés dans les méthodes de la collection encapsulée.

Comme on pouvait s'y attendre, les collections synchronisées assurent la cohérence / intégrité des données dans des environnements multithreads. Cependant, ils peuvent être pénalisés en termes de performances, puisqu'un seul thread peut accéder à la collection à la fois (a.k.a. accès synchronisé).

Pour un guide détaillé sur l'utilisation des méthodes et des blocssynchronized, veuillez consulterour article sur le sujet.

8.2. Collections simultanées

Concurrent collections (e.g. ConcurrentHashMap), achieve thread-safety by dividing their data into segments. Dans unConcurrentHashMap, par exemple, différents threads peuvent acquérir des verrous sur chaque segment, de sorte que plusieurs threads peuvent accéder auxMap en même temps (a.k.a. accès simultané).

Les collections simultanées sontmuch more performant than synchronized collections, en raison des avantages inhérents à l'accès aux threads simultanés.

Ainsi, le choix du type de collection thread-safe à utiliser dépend des exigences de chaque cas d'utilisation et doit être évalué en conséquence.

9. Conclusion

In this article, we took an in-depth look at the set of synchronization wrappers implemented within the Collections class.

De plus, nous avons mis en évidence les différences entre les collections synchronisées et simultanées, ainsi que les approches qu'elles implémentent pour atteindre la sécurité des threads.

Comme d'habitude, tous les exemples de code présentés dans cet article sont disponibles surGitHub.