Remise à niveau en programmation objets en Java

Les collections

La JVM (java Virtual Machine) offre plusieurs solutions pour représenter des collections d'objets. Nous avons six interfaces et de multiples implémentations qui présentes des avantages et des inconvénients :

  • Collection
    • List
    • Set
      • SortedSet
  • Map
    • SortedMap

Vous allez suivre un cours qui est très (trop) complet. Je vous demande vous focaliser sur deux implémentations des List (LinkedList et ArrayList), deux implémentations des Set (HashSet et LinkedHashSet) et un implémentation des Map (HashMap).

Suivre ce tutoriel.

Record

Lire cette documentation et faire fonctionner les exemples présentés.

Enum

Lire les sections 1 à 5 de cette documentation et faire fonctionner les exemples présentés.

Thread

Création

Travail à faire : Commencer par lire et tester les exemples présentés dans les sections 37.1 à 37.3 de cette documentation.
Travail à faire : Continuer en utilisant, dans les exemples, une lambda expression pour déclarer le code exécuté par le thread.
Travail à faire : Continuer en utilisant, dans les exemples, une méthode (utilisée comme une lambda expression) pour déclarer le code exécuté par le thread.

Section critique

Travail à faire : Construire un classe Counter avec une valeur counter de type long, un getter et une méthode d'incrémentation.
Travail à faire : Dans un TU, créer une instance de cette classe et 40 threads qui vont tous, en parallèle, effectuer 1000 incrémentations. Normalement, une fois les threads terminés (méthode join sur les threads), le compteur doit être à 40000. Ce n'est pas le cas. Pourquoi ?
Travail à faire : Solution 1 : Ajouter la clause synchonized à la déclaration de la méthode d'incrémentation. Cette méthode est maintenant une section critique, c'est-à-dire qu'un seul thread peut l'exécuter simultanément. Nous n'avons donc plus de problème de parallélisme.
Travail à faire : Solution 2 : Ajouter à votre classe Counter une variable d'instance mutex de type Semaphore. Dans le constructeur, initialiser cette variable avec new Semaphore(1). Pourquoi la valeur un ? Créer ensuite la méthode incrementWithSemaphore ci-dessous :
void incrementWithSemaphore() {
    try {
        s.acquire(); // pour protéger le compteur
        counter++;
        s.release(); // pour libérer le sémaphore
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
Vérifier que tout est OK.
Travail à faire : Solution 3 : Créer un méthode incrementWithAtomicInteger. Ajouter une variable d'instance à Counter de type AtomicInteger. C'est une structure de données qui supporte les opérations sur les entiers réalisées en parallèle. Utiliser incrementWithAtomicInteger à la place de increment.

Le problème du producteur et du consommateur

Dans ce problème, nous avons deux threads. Le premier fabrique des objets (des entiers par exemple) et les place dans un tampon. Le second les consomme, par exemple, pour les afficher. Il faut donc synchroniser l'activité de production et de consommation.

Dans un test unitaire, réaliser les actions suivantes :

  • Préparer l'instance ci-dessous (un buffer synchronisé utilisable dans un environnement multi-thread).
    BlockingQueue<Integer> buffer = new LinkedBlockingQueue<>();
    
  • Préparer un thread qui va ajouter au buffer (add) les entiers de 1 à 500.
  • Préparer un autre thread qui va, en parallèle, récupérer 500 fois une valeur dans le buffer avec
    // opération bloquante si le buffer est vide
    var v = buffer.poll(1L, TimeUnit.DAYS);
    
  • Ajouter des affichages afin de suivre la production et la consommation. Elles doivent se dérouler en parallèle. Vous pouvez ralentir le thread producteur avec le code ci-dessous :
    try {
        Thread.sleep(100L);
    } catch (InterruptedException e) {
    }
    
  • Vous pouvez également lancer plusieurs instances du producteur et plusieurs instances du consommateur.