JavaRush /Blog Java /Random-FR /Niveau 26. Réponses aux questions d'entretien sur le suje...
zor07
Niveau 31
Санкт-Петербург

Niveau 26. Réponses aux questions d'entretien sur le sujet du niveau. Partie 2. Questions 6-9, 11-12

Publié dans le groupe Random-FR
Niveau 26. Réponses aux questions d'entretien sur le sujet du niveau.  Partie 2. Questions 6-9, 11-12 - 1

6. Qu’est-ce que Kankarensi ?

La concurrence est une bibliothèque de classes en Java qui contient des classes spéciales optimisées pour travailler sur plusieurs threads. Ces cours sont regroupés dans un package java.util.concurrent. Elles peuvent être schématiquement divisées en fonction des fonctionnalités comme suit : Niveau 26. Réponses aux questions d'entretien sur le sujet du niveau.  Partie 2. Questions 6-9, 11-12 - 2Collections simultanées - un ensemble de collections qui fonctionnent plus efficacement dans un environnement multithread que les collections universelles standard du java.utilpackage. Au lieu d'un wrapper de base Collections.synchronizedListbloquant l'accès à l'ensemble de la collection, des verrous sur des segments de données sont utilisés ou le travail est optimisé pour la lecture parallèle des données à l'aide d'algorithmes sans attente. Files d'attente - files d'attente non bloquantes et bloquantes avec prise en charge multi-thread. Les files d'attente non bloquantes sont conçues pour la vitesse et le fonctionnement sans bloquer les threads. Les files d'attente de blocage sont utilisées lorsque vous devez « ralentir » les threads « Producteur » ou « Consommateur » si certaines conditions ne sont pas remplies, par exemple, la file d'attente est vide ou débordée, ou il n'y a pas de « Consommateur » libre. Les synchroniseurs sont des utilitaires auxiliaires pour synchroniser les threads. Ils constituent une arme puissante dans l’informatique « parallèle ». Exécuteurs - contient d'excellents frameworks pour créer des pools de threads, planifier des tâches asynchrones et obtenir des résultats. Locks - représente des mécanismes de synchronisation de threads alternatifs et plus flexibles par rapport aux mécanismes synchronizedde waitbase notify. Atomique - classes prenant en charge les opérations atomiques sur les primitives et les références. Source:notifyAll

7. Quels cours de Kankarensi connaissez-vous ?

La réponse à cette question est parfaitement exposée dans cet article . Je ne vois pas l’intérêt de tout réimprimer ici, je ne donnerai donc des descriptions que des cours avec lesquels j’ai eu l’honneur de me familiariser brièvement. ConcurrentHashMap<K, V> - Contrairement Hashtableaux blocs et synhronizedsur HashMap, les données sont présentées sous forme de segments, divisés en hachages de clés. En conséquence, les données sont accessibles par segments plutôt que par objet unique. De plus, les itérateurs représentent les données pour une période de temps spécifique et ne lancent pas de fichiers ConcurrentModificationException. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - Et si dans une classe vous deviez synchroniser l'accès à une variable simple de typeint ? Vous pouvez utiliser des constructions avec synchronized, et lors de l'utilisation d'opérations atomiques set/get, volatile. Mais vous pouvez faire encore mieux en utilisant de nouvelles classes Atomic*. Grâce à l'utilisation de CAS, les opérations avec ces classes sont plus rapides que si elles étaient synchronisées via synchronized/volatile. De plus, il existe des méthodes d'addition atomique d'une quantité donnée, ainsi que d'incrémentation/décrémentation.

8. Comment fonctionne la classe ConcurrentHashMap ?

Au moment de son introduction, ConcurrentHashMaples développeurs Java avaient besoin de l'implémentation de hash map suivante :
  • Sécurité du fil
  • Aucun verrou sur toute la table lors de l'accès
  • Il est souhaitable qu'il n'y ait pas de verrous de table lors de l'exécution d'une opération de lecture
Les principales idées de mise en œuvre ConcurrentHashMapsont les suivantes :
  1. Éléments de la carte

    Contrairement aux éléments HashMap, Entryin ConcurrentHashMapsont déclarés comme volatile. Il s'agit d'une fonctionnalité importante, également en raison des modifications apportées à JMM .

    static final class HashEntry<K, V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K, V> next;
    
        HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
            this .key = key;
            this .hash = hash;
            this .next = next;
            this .value = value;
         }
    
        @SuppressWarnings("unchecked")
        static final <K, V> HashEntry<K, V>[] newArray(int i) {
            return new HashEntry[i];
        }
    }
  2. Fonction de hachage

    ConcurrentHashMapune fonction de hachage améliorée est également utilisée.

    Laissez-moi vous rappeler à quoi cela ressemblait dans HashMaple JDK 1.2 :

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    Version de ConcurrentHashMap JDK 1.5 :

    private static int hash(int h) {
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }

    Pourquoi est-il nécessaire de rendre la fonction de hachage plus complexe ? Les tables d'une carte de hachage ont une longueur déterminée par une puissance de deux. Pour les codes de hachage dont les représentations binaires ne diffèrent pas en position basse et haute, nous aurons des collisions. Augmenter la complexité de la fonction de hachage résout simplement ce problème, réduisant ainsi le risque de collisions sur la carte.

  3. Segments

    La carte est divisée en N segments différents (16 par défaut, la valeur maximale peut être de 16 bits et est une puissance de deux). Chaque segment est une table thread-safe d’éléments de carte. L'augmentation du nombre de segments encouragera les opérations de modification à s'étendre sur plusieurs segments, réduisant ainsi le risque de blocage au moment de l'exécution.

  4. Niveau de concurrence

    Ce paramètre affecte l'utilisation de la carte mémoire et le nombre de segments dans la carte.

    Le nombre de segments sera choisi comme la puissance de deux la plus proche supérieure à ConcurrencyLevel. En réduisant le niveau de concurrence, il est plus probable que les threads bloquent les segments de carte lors de l'écriture. La surestimation de l'indicateur conduit à une utilisation inefficace de la mémoire. Si un seul thread modifie la carte et que les autres lisent, il est recommandé d'utiliser la valeur 1.

  5. Total

    Ainsi, les principaux avantages et fonctionnalités de mise en œuvre ConcurrentHashMap:

    • hashmapLa carte possède une interface d'interaction similaire à
    • Les opérations de lecture ne nécessitent pas de verrous et sont effectuées en parallèle
    • Les opérations d'écriture peuvent souvent également être effectuées en parallèle sans bloquer
    • Lors de la création, celui recherché est indiqué concurrencyLevel, déterminé par les statistiques de lecture et d'écriture
    • Les éléments de la carte ont une valeur valuedéclarée commevolatile
    Source :  Comment fonctionne ConcurrentHashMap

9. Qu'est-ce que la classe Lock ?

Pour contrôler l'accès à une ressource partagée, nous pouvons utiliser des verrous comme alternative à l'opérateur synchronisé. La fonctionnalité de verrouillage est fournie dans java.util.concurrent.locks. Tout d’abord, le thread tente d’accéder à la ressource partagée. S'il est libre, un verrou est placé sur le fil. Une fois le travail terminé, le verrou sur la ressource partagée est libéré. Si la ressource n'est pas libre et qu'un verrou est déjà placé dessus, alors le thread attend que ce verrou soit libéré. Les classes de verrouillage implémentent une interface Lockqui définit les méthodes suivantes :
  • void lock():attend que le verrou soit acquis
  • boolean tryLock():essaie d'acquérir un verrou ; si le verrou est obtenu, il renvoie true . Si le verrou n'est pas acquis, il renvoie false . Contrairement à la méthode, lock()elle n'attend pas pour acquérir un verrou si celui-ci n'est pas disponible
  • void unlock():enlève le verrou
  • Condition newCondition():renvoie l'objet Conditionassocié au verrou actuel
L'organisation du verrouillage dans le cas général est assez simple : pour obtenir le verrou, la méthode est appelée lock(), et après avoir fini de travailler avec les ressources partagées, la méthode est appelée unlock(), qui libère le verrou. L'objet Conditionpermet de gérer le blocage. En règle générale, pour travailler avec les verrous, une classe ReentrantLockdu package est utilisée. java.util.concurrent.locks.Cette classe implémente l'interface Lock. Examinons l'utilisation de l'API Java Lock en utilisant un petit programme comme exemple : disons que nous avons une classe Resourceavec quelques méthodes thread-safe et des méthodes où la sécurité des threads n'est pas requise.
public class Resource {

    public void doSomething(){
        // пусть здесь происходит работа с базой данных
    }

    public void doLogging(){
        // потокобезопасность для логгирования нам не требуется
    }
}
Prenons maintenant une classe qui implémente l'interface Runnableet utilise les méthodes de classe Resource.
public class SynchronizedLockExample implements Runnable{

    // экземпляр класса Resource для работы с методами
    private Resource resource;

    public SynchronizedLockExample(Resource r){
        this.resource = r;
    }

    @Override
    public void run() {
        synchronized (resource) {
            resource.doSomething();
        }
        resource.doLogging();
    }
}
Réécrivons maintenant le programme ci-dessus en utilisant l'API Lock au lieu du synchronized.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{

    private Resource resource;
    private Lock lock;

    public ConcurrencyLockExample(Resource r){
        this.resource = r;
        this.lock = new ReentrantLock();
    }

    @Override
    public void run() {
        try {
            // лочим на 10 секунд
            if(lock.tryLock(10, TimeUnit.SECONDS)){
            resource.doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //убираем лок
            lock.unlock();
        }
        // Для логгирования не требуется потокобезопасность
        resource.doLogging();
    }

}
Comme vous pouvez le voir sur le programme, nous utilisons la méthode tryLock()pour nous assurer que le thread n'attend qu'un certain temps. S'il n'obtient pas de verrou sur l'objet, il se connecte simplement et quitte. Autre point important. Vous devez utiliser un bloc try-finallypour garantir que le verrou sera libéré même si la méthode doSomething()lève une exception. Sources:

11. Qu'est-ce qu'un mutex ?

Un mutex est un objet spécial pour synchroniser les threads/processus. Cela peut prendre deux états : occupé et libre. Pour simplifier, un mutex est une variable booléenne qui prend deux valeurs : occupé (vrai) et libre (faux). Lorsqu'un thread souhaite la propriété exclusive d'un objet, il marque son mutex comme étant occupé, et lorsqu'il a fini de travailler avec lui, il marque son mutex comme libre. Un mutex est attaché à chaque objet en Java. Seule la machine Java a un accès direct au mutex. Il est caché au programmeur.

12. Qu'est-ce qu'un moniteur ?

Un moniteur est un mécanisme spécial (un morceau de code) - un module complémentaire sur le mutex, qui garantit son bon fonctionnement. Après tout, il ne suffit pas de marquer que l'objet est occupé ; il faut également s'assurer que d'autres threads n'essaient pas d'utiliser l'objet occupé. En Java, le moniteur est implémenté à l'aide du mot-clé synchronized. Lorsque nous écrivons un bloc synchronisé, le compilateur Java le remplace par trois morceaux de code :
  1. Au début du bloc synchronized, du code est ajouté qui marque le mutex comme étant occupé.
  2. À la fin du bloc, synchronizedun code est ajouté qui marque le mutex comme libre.
  3. Avant le bloc, synchronizeddu code est ajouté qui vérifie si le mutex est occupé, le thread doit alors attendre qu'il soit libéré.
Partie 1
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION