Le multithread JAVA est expliqué en détail. Si vous ne comprenez pas, contactez-moi.

1. Bases du multithread

1.1 Fils et processus
processus:

Fait référence à une application exécutée en mémoire. Chaque processus dispose d'un espace mémoire indépendant. Une application peut exécuter plusieurs processus en même temps. Un processus est également un processus d'exécution d'un programme et constitue l'unité de base permettant au système d'exécuter des programmes. le système exécute un programme. Un programme est le processus depuis la création, le fonctionnement jusqu'à la mort d'un processus.

Fil de discussion:

Unité d'exécution indépendante au sein d'un processus ; un processus peut exécuter plusieurs threads simultanément en même temps. On peut comprendre qu'un processus est équivalent à un système d'exploitation à processeur unique et que les threads sont plusieurs tâches exécutées dans ce système.

2. Comment créer des multi-threads

La première consiste à hériter de la classe Thread et à remplacer la méthode run (la valeur de retour ne peut pas être définie)

  • Créez une java.lang.Threadsous-classe qui hérite de la classe et remplacez run()les méthodes pour définir les tâches effectuées par le thread. Ensuite, créez une instance de la sous-classe et appelez start()la méthode pour démarrer le thread.

class MyThread extends Thread {
    public void run() {
        
    }
}

MyThread thread = new MyThread();
thread.start();

La deuxième méthode implémente l'interface Runnable et remplace la méthode run (la valeur de retour ne peut pas être définie)

  • Créez une classe qui implémente java.lang.Runnablel'interface et implémente ses run()méthodes pour définir les tâches effectuées par le thread. Ensuite, créez un Threadobjet, Runnabletransmettez-lui l'objet et enfin appelez start()la méthode pour démarrer le thread.

class MyRunnable implements Runnable {
    public void run() {
        
    }
}

Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

La troisième implémentation implémente l'interface Callable (l'objet de valeur de retour du thread peut exister)

  • Créez une java.util.concurrent.Callableclasse qui implémente l'interface, implémente ses call()méthodes pour définir les tâches effectuées par le thread et peut renvoyer un résultat. Utiliser ExecutorServicepour soumettre Callabledes tâches et obtenir des résultats d'exécution.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<String> {
    public String call() {
        
        return "Task completed";
    }
}

ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<String> callable = new MyCallable();
Future<String> future = executor.submit(callable);

Avantages de l'implémentation de l'interface Runnable par rapport à l'héritage de la classe Thread :
  1. Convient à plusieurs threads du même code de programme pour partager la même ressource.

  2. Les limitations de l'héritage unique en Java peuvent être évitées.

  3. Augmentez la robustesse du programme et réalisez des opérations découplées. Le code peut être partagé par plusieurs threads et le code et les données sont indépendants.

  4. Le pool de threads ne peut être placé que dans des threads qui implémentent des classes Runable ou appelables, et ne peut pas être directement placé dans des classes qui héritent de Thread.

Utiliser le pool de threads :

  • Le pool de threads est une méthode de gestion multithread plus avancée qui peut réutiliser les threads pour effectuer plusieurs tâches. Utilisez ExecutorServicedes interfaces pour créer et gérer des pools de threads, puis submit()soumettez des tâches via des méthodes.

  1. import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    ExecutorService executor = Executors.newFixedThreadPool(2); 
    Runnable runnable = () -> {
        
    };
    executor.submit(runnable);
    
    

Chacune de ces méthodes peut être utilisée pour créer du multithreading, et le choix spécifique dépend de vos besoins et de votre conception. Les pools de threads constituent un moyen efficace de réduire les frais de création et de destruction de threads et de mieux gérer le cycle de vie des threads. Dans le même temps, Callablel'interface peut être utilisée pour obtenir les résultats d'exécution de la tâche et Runnableelle est utilisée pour effectuer des tâches qui n'ont pas besoin de renvoyer de résultats.

Flux de travail du pool de threads

Un pool de threads est un mécanisme de gestion et de réutilisation des threads, qui peut améliorer les performances et l'efficacité de la gestion des ressources des applications multithread. Voici un workflow typique de pool de threads :

  1. Initialisez le pool de threads :

    • Créez un pool de threads et initialisez ses paramètres, notamment le nombre minimum de threads, le nombre maximum de threads, la taille de la file d'attente des tâches, le temps d'inactivité des threads, etc. La taille du pool de threads est généralement déterminée en fonction des exigences de l'application et des ressources système.

  2. Soumettre la tâche :

    • Lorsqu'une tâche doit être exécutée, elle est soumise au pool de threads. Une tâche peut être un objet Runnableou Callableun objet qui représente une unité de travail qui doit être effectuée.

  3. File d'attente des tâches :

    • Le pool de threads maintient une file d'attente de tâches et toutes les tâches soumises sont mises en file d'attente dans cette file d'attente en attente d'exécution. S'il y a des threads disponibles dans le pool de threads, ils prendront la tâche de la file d'attente et l'exécuteront. Si aucun thread n'est disponible, la tâche attend qu'un thread soit disponible.

  4. Tâches d'exécution du thread :

    • Les threads du pool de threads récupéreront cycliquement les tâches de la file d’attente des tâches et les exécuteront. Une fois la tâche terminée, le thread reviendra au pool de threads, prêt à effectuer la tâche suivante.

  5. Réutilisation du fil :

    • Le pool de threads réutilise les threads au lieu de les détruire après chaque tâche. Cela réduit la surcharge de création et de destruction des threads et améliore l’efficacité de l’exécution.

  6. Gestion du pool de threads :

    • Le pool de threads est responsable de la gestion du nombre et de l’état des threads. Il peut ajuster dynamiquement le nombre de threads selon les besoins pour s'adapter à différentes charges de travail. Par exemple, le nombre de threads peut être augmenté ou diminué en fonction du nombre de tâches dans la file d'attente.

  7. Mission accomplie :

    • Lorsque l'exécution de la tâche est terminée, le résultat de l'exécution de la tâche peut être obtenu (si la tâche est Callablede type). Les résultats peuvent ensuite être traités ou renvoyés à l'appelant.

  8. Fermez le pool de threads :

    • Lorsqu'un pool de threads n'est plus nécessaire, il doit être fermé explicitement. La fermeture du pool de threads cessera d’accepter de nouvelles tâches et attendra la fin des tâches soumises. Ensuite, les threads du pool de threads seront terminés. Fermer le pool de threads consiste à libérer des ressources et à éviter les fuites de mémoire.

Le principal avantage du pool de threads est qu'il peut gérer et réutiliser efficacement les threads, réduire les frais de création et de destruction des threads et améliorer les performances et la vitesse de réponse de l'application. Il peut également contrôler le nombre de threads simultanés pour éviter les problèmes d'épuisement des ressources. Par conséquent, l’utilisation d’un pool de threads est généralement une bonne pratique dans les applications multithread.

import java.util.concurrent.*;

public class ThreadPoolExample {

    public static void main(String[] args) {
        
        ThreadFactory threadFactory = Executors.defaultThreadFactory();

        
        RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

        
        int corePoolSize = 5;
        int maxPoolSize = 10;
        long keepAliveTime = 60; 
        TimeUnit unit = TimeUnit.SECONDS; 
        int queueCapacity = 100; 

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maxPoolSize,
                keepAliveTime,
                unit,
                new LinkedBlockingQueue<>(queueCapacity),
                threadFactory,
                rejectedExecutionHandler
        );

        
        for (int i = 0; i < 10; i++) {
            final int taskId = i; 
            executor.execute(new Runnable() {
                public void run() {
                    System.out.println("Task " + taskId + " is executing by " +
                            Thread.currentThread().getName());
                    
                    
                }
            });
        }

        
        executor.shutdown();
    }
}

Dans cet exemple, nous Executors.defaultThreadFactory()créons d’abord une fabrique de threads par défaut pour créer des threads dans le pool de threads.

Ensuite, nous avons créé une politique de rejet ThreadPoolExecutor.AbortPolicy(), ce qui signifie que lorsque le pool de threads est saturé (le pool de threads et la file d'attente des tâches sont pleins), nous refusons d'accepter de nouvelles tâches et levons RejectedExecutionExceptionune exception.

Enfin, nous ThreadPoolExecutortransmettons la fabrique de threads et la politique de rejet comme paramètres supplémentaires lors de la création.

En personnalisant la fabrique de threads et la politique de rejet, nous pouvons contrôler de manière plus flexible le processus de création de threads dans le pool de threads et le traitement de rejet des tâches.

3. Fil démon

Il existe deux types de threads en Java, l’un est le thread utilisateur et l’autre est le thread démon. Les threads utilisateur font référence aux threads créés par les utilisateurs. Lorsque le thread principal s'arrête, le thread utilisateur ne s'arrêtera pas. Thread démon Lorsque le processus n'existe pas ou que le thread principal est arrêté, le thread démon sera également arrêté.

Un thread démon est un thread spécial exécuté dans un programme informatique. Sa principale caractéristique est que lorsque tous les threads non-démons se terminent, le thread démon se fermera automatiquement sans attendre la fin de la tâche.

Les threads démons sont généralement utilisés pour effectuer certaines tâches en arrière-plan, telles que le garbage collection, la journalisation, etc. Ils effectuent des tâches en silence pendant l'exécution du programme, sans bloquer l'exécution du thread principal ou d'autres threads non démons.

Contrairement aux threads ordinaires, le cycle de vie du thread démon n’affecte pas le cycle de vie de l’ensemble du programme. Lorsque tous les threads non démons se terminent, le thread démon sera forcé de se fermer, que sa tâche soit terminée ou non.

Il convient de noter que les threads démons ne peuvent pas être utilisés pour effectuer certaines tâches importantes car ils peuvent être forcés de se fermer à tout moment. De plus, les threads démons ne peuvent pas intercepter ou gérer les exceptions.

En résumé, un thread démon est un thread qui effectue des tâches en arrière-plan et se fermera automatiquement à la fin de tous les threads non démons. Ils sont généralement utilisés pour effectuer certaines tâches sans importance ou périodiques.

thread1.setDaemon(true); 

4. Problèmes liés à la sécurité des threads

Les problèmes de sécurité des threads sont causés par des variables globales et des variables statiques. S'il n'y a que des opérations de lecture pour les variables globales et les variables statiques dans chaque thread, mais aucune opération d'écriture, en général, cette variable globale est thread-safe ; si plusieurs threads effectuent des opérations d'écriture en même temps, la synchronisation des threads doit généralement être prise en compte. , sinon cela peut affecter la sécurité des threads.

5.Comment résoudre

Lorsque nous utilisons plusieurs threads pour accéder à la même ressource et qu'il existe des opérations d'écriture sur la ressource dans plusieurs threads, des problèmes de sécurité des threads sont susceptibles de se produire. Pour résoudre le problème de sécurité ci-dessus de l'accès simultané à une ressource par plusieurs threads, Java fournit un mécanisme de synchronisation (synchronisé) pour le résoudre.

1. Bloc de code synchronisé (verrouillage automatique) (verrouillage par poids)

2. Méthode de synchronisation

3. Synchronisation du verrouillage (verrouillage manuel)

ReentrantLock lock = new ReentrantLock()
 lock.lock()
 sell(name)
 lock.unlock()

Question d'entretien : jeu d'instructions JVM

Quel verrou a de meilleures performances, verrouillage ou synchronisation ? Avant la version 1.8, le verrouillage était plus fort. Dans la version 1.8 (y compris), il n'y avait aucune différence entre syn et lock.

  1. Quelle est la différence entre les blocs de code synchronisés et les méthodes synchronisées ? L'objet verrou des différentes méthodes de synchronisation est le suivant. L'objet verrou du bloc de code de synchronisation est n'importe quel objet (doit être unique)

  2. Principe de mise en œuvre synchronisée ? instructions de bytecode MonitorEnter et MonitorExit

  3. Quelle est la différence entre verrouillé et synchronisé ?

  4. Le verrou est-il un verrou optimiste ou un verrou pessimiste ? Dépend de la classe d'implémentation ReentrantLock Verrou pessimiste Verrou en lecture-écriture Verrou optimiste

  5. ReentrantLock est-il un verrou équitable ou un verrou injuste ? Aucune participation n'est injuste, la participation au nom d'autrui est équitable.

L'utilisation de verrous entraînera ---- une impasse : les threads s'attendent les uns les autres.

Blocage multithread : la synchronisation est imbriquée dans la synchronisation, ce qui empêche le verrouillage de se libérer

Comment éviter : essayez d’imbriquer les verrous dans les verrous

6. Statut du fil de discussion

Description du statut : NEW (nouveau) : Le fil de discussion vient d'être créé, mais n'a pas été démarré.

RUNNABLE : État dans lequel un thread peut s'exécuter dans la machine virtuelle Java. Il peut ou non exécuter son propre code, selon le processeur du système d'exploitation.

BLOQUÉ : lorsqu'un thread tente d'acquérir un verrou d'objet et que le verrou d'objet est détenu par un autre thread, le thread entre dans l'état Bloqué ; lorsque le thread détient le verrou, le thread passe à l'état Exécutable.

WAITING (attente infinie) : lorsqu'un thread attend qu'un autre thread effectue une action (de réveil), le thread entre dans l'état d'attente. Après être entré dans cet état, il ne peut pas être réveillé automatiquement. Vous devez attendre qu'un autre thread appelle la méthode notify ou notifyAll avant de vous réveiller.

TIMED_WAITING (timed waiting) : identique à l'état d'attente, plusieurs méthodes ont des paramètres de délai d'attente, et leur appel entrera dans l'état Timed Waiting. Cet état restera jusqu'à l'expiration du délai d'attente ou jusqu'à ce qu'une notification de réveil soit reçue. Les méthodes couramment utilisées avec des paramètres de délai d'attente incluent Thread.sleep et Object.wait.

TERMINATED (terminé) : est mort parce que la méthode run s'est terminée normalement, ou est mort parce qu'une exception non interceptée a mis fin à la méthode run.

wait() met le thread dans un état d'attente et nécessite un réveil manuel pour libérer la ressource de verrouillage actuelle.
sleep() ne libérera pas le verrou, permettant au thread de se réveiller naturellement dans un état d'attente
  • Pour la méthode sleep(), il faut d’abord savoir que cette méthode appartient à la classe Thread. La méthode wait() appartient à la classe Object.

  • La méthode sleep() amène le programme à suspendre l'exécution pendant le temps spécifié et à céder le CPU à d'autres threads, mais son état de surveillance est toujours maintenu. Lorsque le temps spécifié est écoulé, il reprendra automatiquement son état d'exécution.

    wait() cède le contrôle, puis entre dans le pool de verrouillage d'objet en attente de cet objet dans un état d'attente. Ce n'est qu'après avoir appelé la méthode notify() pour cet objet que ce thread entre dans le pool de verrouillage d'objet pour se préparer à acquérir le verrou d'objet et Entrez dans l'état de fonctionnement.

  • Lors de l’appel à la méthode sleep(), le thread ne libérera pas le verrou de l’objet. Lorsque la méthode wait() est appelée, le thread abandonnera le verrouillage de l'objet.

7. Fin du fil :

Il existe trois manières de terminer un thread : (1) Définissez l'indicateur de sortie pour que le thread se termine normalement. (2) Utilisez la méthode interruption() pour interrompre le thread. (3) Utilisez la méthode stop pour forcer l'arrêt du thread (l'utilisation de Thread.stop n'est pas recommandée, cette méthode d'arrêt du thread en cours d'exécution a été abandonnée et son utilisation est extrêmement dangereuse !)

Thread.sleep(1000l);

t.interrupt();
t.stop(); 

8. Priorité du fil

Les systèmes d'exploitation actuels utilisent essentiellement le partage de temps pour planifier l'exécution des threads. Le nombre de tranches de temps allouées à un thread détermine la quantité de ressources processeur que le thread utilise et correspond également au concept de priorité des threads.

Dans le thread JAVA, la priorité est contrôlée via une priorité int, allant de 1 à 10, 10 étant la plus élevée et la valeur par défaut étant 5.

La priorité des threads ne reflète pas l'ordre d'exécution des threads, elle permet simplement au thread actuel d'obtenir plus de ressources CPU.

La priorité peut augmenter la quantité de ressources CPU qu'un thread obtient, mais elle ne peut pas déterminer l'ordre d'exécution des threads.

t.setPriority(1);  

Méthode join() (laissez les threads s'exécuter séquentiellement)

La fonction de join est de faire attendre les autres threads. thread.Join ajoute le thread spécifié au thread actuel et peut fusionner deux threads s'exécutant alternativement en threads d'exécution séquentielle. Par exemple, si la méthode Join() du thread A est appelée dans le thread B, le thread B ne continuera pas à s'exécuter jusqu'à ce que le thread A termine son exécution.

méthode de rendement

Le rôle de la méthode Thread.yield() : mettre en pause le thread en cours d'exécution et exécuter d'autres threads. (Peut n'avoir aucun effet) rendement() renvoie le thread en cours d'exécution à un état exécutable pour permettre à d'autres threads avec la même priorité d'avoir une chance de s'exécuter. Par conséquent, le but de l’utilisation de rendement() est de permettre une rotation appropriée de l’exécution entre les threads ayant la même priorité. Cependant, en pratique, il n'y a aucune garantie que rendement() atteindra l'objectif de rendement, car le thread produisant peut être à nouveau sélectionné par le planificateur de threads.

9. Trois caractéristiques de la concurrence multithread (points clés)

Atomicité : c'est-à-dire qu'une ou plusieurs opérations sont soit entièrement exécutées et le processus d'exécution n'est interrompu par aucun facteur, soit elles ne sont pas exécutées du tout.

Visibilité : lorsque plusieurs threads accèdent à la même variable et qu'un thread modifie la valeur de la variable, les autres threads peuvent immédiatement voir la valeur modifiée (volitale)

Ordre : l'ordre d'exécution du programme est basé sur l'ordre d'exécution du code.

Solution aux problèmes de visibilité :

1. Résoudre le problème de visibilité de manière synchrone

while (flag) {
            synchronized (this) {
            }
        }

Avant que le thread ne soit déverrouillé (à la sortie du bloc de code synchronisé) : la dernière valeur de la variable partagée dans sa propre mémoire de travail doit être actualisée dans la mémoire principale.

Lorsque le thread se verrouille (lors de la saisie du bloc de code synchronisé) : la valeur de la variable partagée dans la mémoire locale sera effacée, donc lors de l'utilisation de la variable partagée, vous devez relire la dernière valeur de la mémoire principale (verrouillage et le déverrouillage est le même verrou)

verrouillage de la rotation

Le soi-disant verrouillage de rotation consiste à laisser le thread attendre pendant un certain temps sans être immédiatement suspendu pour voir si le thread qui maintient le verrou le libérera bientôt. Comment attendre ? Exécutez simplement une boucle dénuée de sens (spin).

S'adapter au verrouillage rotatif

C’est-à-dire un verrouillage de rotation adaptatif. Ce qu'on appelle adaptatif signifie que le nombre de tours n'est plus fixe, il est déterminé par le temps de rotation précédent sur la même écluse et le statut du propriétaire de la serrure.

Élimination des verrous (implémentation de l'optimisation JDK Object Syn)

Afin de garantir l'intégrité des données, nous devons effectuer un contrôle de synchronisation sur cette partie de l'opération lors de l'exécution des opérations. Cependant, dans certains cas, la JVM détecte qu'il n'y a pas de concurrence de données partagées, c'est pourquoi la JVM éliminera ces verrous de synchronisation. La base de l'élimination des verrous est la prise en charge des données de l'analyse d'échappement.

La JVM peut clairement détecter que le vecteur variable ne s'est pas échappé de la méthode vectorTest(), de sorte que la JVM peut éliminer audacieusement l'opération de verrouillage à l'intérieur du vecteur.

À propos de la définition de l'analyse d'échappement Java :

Escape Analysis (Escape Analysis) est simplement une technologie qui permet à la machine virtuelle Java Hotspot d'analyser la plage d'utilisation des objets nouvellement créés et de décider si elle doit allouer de la mémoire sur le tas Java.

Verrouillez le langage grossier

Cependant, une série d'opérations continues de verrouillage et de déverrouillage peut entraîner une perte de performances inutile, c'est pourquoi le concept de grossissement du verrouillage est introduit.

Il s'agit de connecter plusieurs opérations de verrouillage et de déverrouillage consécutives et de les étendre en un verrou plus grand.

Verrouillage du poids (SYN)

Le système d'exploitation doit implémenter la commutation entre les threads de
Le coût de passage du mode utilisateur au mode noyau est très élevé.

10.Introduction volatile (point d'entretien)

Question d'entretien : volatile peut-il garantir la sécurité des threads ? Pourquoi?

Non, volatile ne peut garantir que la visibilité et l'ordre, pas l'atomicité.

Fonction : Résoudre le problème de visibilité de la mémoire
public volatile boolean flag = true;

Le processus de mise en œuvre de la visibilité de la mémoire avec Volatile

Le processus d'écriture de threads de variables volatiles :
  1. Changer la valeur d'une copie d'une variable volatile dans la mémoire locale du thread ;

  2. Vide la valeur de copie modifiée de la mémoire locale vers la mémoire principale

Le processus de lecture des threads des variables volatiles :
  1. Lire la dernière valeur de la variable volatile de la mémoire principale vers la mémoire locale du thread

  2. Lire une copie d'une variable volatile à partir de la mémoire locale

Volatile implémente le principe de visibilité de la mémoire :

Pendant une opération d'écriture, une instruction de barrière de stockage est ajoutée après l'instruction d'opération d'écriture de sorte que la valeur de la variable dans la mémoire locale puisse être rafraîchie dans la mémoire principale.

Pendant l'opération de lecture, en ajoutant une instruction de barrière de charge avant l'opération de lecture, la valeur de la variable dans la mémoire principale peut être lue dans le temps.

Volatile ne peut pas garantir l'atomicité

solution:

  1. Utilisation synchronisée (non recommandée)

    public synchronized void addCount() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }
    
    
  2. Utiliser ReentrantLock (verrouillage réentrant)

    private Lock lock = new ReentrantLock();
    ​
    public void addCount() {
        for (int i = 0; i < 10000; i++) {
            lock.lock();
            count++;
            lock.unlock();
        }
    }
    
    
  3. Utilisation d'AtomicInteger (opérations atomiques)

public static AtomicInteger count = new AtomicInteger(0);
public void addCount() {
    for (int i = 0; i < 10000; i++) {
        
        count.incrementAndGet();
    }
}

Introduction au CAS

Qu’est-ce que le CAS ?

CAS : Compare and Swap, c'est-à-dire comparer et échanger.

jdk5 ajoute le package concurrent java.util.concurrent.*, et les classes suivantes utilisent l'algorithme CAS pour implémenter un verrou optimiste différent du verrou de synchronisation synchrone. Avant JDK 5, le langage Java s'appuyait sur le mot-clé synchronisé pour assurer la synchronisation. Il s'agit d'un verrou exclusif et d'un verrou pessimiste.

Compréhension de l'algorithme CAS

Comprendre CAS, CAS est un algorithme sans verrouillage (verrouillage optimiste).CAS a 3 opérandes, la valeur mémoire V, l'ancienne valeur attendue A et la nouvelle valeur à modifier B. Si et seulement si la valeur attendue A et la valeur mémoire V sont identiques, modifiez la valeur mémoire V en B, sinon ne faites rien.

S'il existe trois threads qui souhaitent modifier simultanément la valeur d'un AtomicInteger, leurs mécanismes sous-jacents sont les suivants :

1. Tout d’abord, chaque thread obtiendra d’abord la valeur actuelle. Effectuez ensuite une opération CAS atomique. Atomique signifie que cette opération CAS doit être entièrement exécutée par vous-même et ne sera pas interrompue par d'autres.

2. Ensuite, dans l'opération CAS, elle sera comparée pour voir si votre valeur actuelle correspond à la valeur que je viens d'obtenir. Si c'est le cas, cela signifie que personne n'a modifié cette valeur, vous pouvez alors la définir sur une valeur après avoir accumulé 1.

3. De la même manière, si quelqu'un exécute CAS et constate que la valeur qu'il a obtenue auparavant est différente de la valeur actuelle, cela entraînera l'échec de CAS. Après l'échec, il entrera dans une boucle infinie, obtiendra à nouveau la valeur, puis effectuez l'opération CAS.

Défauts CAS

Bien que CAS résolve efficacement les opérations atomiques, il présente encore quelques défauts, qui se manifestent principalement par trois méthodes : le temps de cycle est trop long, une seule opération atomique à variable partagée peut être garantie et le problème ABA

Il ya un problème:

1. Peut-être que CAS échouera toujours et tournera ensuite

2. Si une valeur était à l'origine A, changée en B, puis à nouveau changée en A, alors lors du contrôle CAS, il s'avère qu'il n'y a eu aucun changement, mais en fait, elle a changé. C'est ce qu'on appelle l'ABA. problème. La solution au problème ABA consiste à ajouter un numéro de version, c'est-à-dire à ajouter un numéro de version à chaque variable et à ajouter 1 à chaque fois qu'elle change, c'est-à-dire A —> B —> A, devient 1A —> 2B —> 3A.

AQS de JAVA

Qu’est-ce que l’AQS ? (acquisition de serrure et déverrouillage)

Ce n'est qu'une classe abstraite, mais de nombreux composants de JUC sont basés sur cette classe abstraite. On peut également dire que cet AQS est la base de la plupart des composants JUC.

Utilisé sous le package JUC, le composant principal AQS (AbstractQueuedSynchronizer), c'est-à-dire le synchroniseur de file d'attente.

Verrou JAVA

ReentrantLock Verrou réentrant (verrouillage pessimiste)

Obtenez le verrou sync.lock();

Libérez le verrou sync.release(1);

La différence entre ReentrantLock et synchronisé

1. Il a plus de fonctions que synchronisées et est plus évolutif.

2. Traitez les opérations d'attente de thread et de réveil avec plus de détails et de flexibilité.

3.ReentrantLock fournit des demandes de verrouillage interrogeables. Il essaiera d'acquérir le verrou, et en cas de succès, continuera, sinon il peut attendre la prochaine exécution, et synchronisé réussira ou bloquera une fois la demande de verrouillage saisie, donc par rapport à synchronisé, ReentrantLock sera moins sujet aux blocages.

4.ReentrantLock prend en charge des blocs de code synchronisés plus flexibles, mais lors de l'utilisation synchronisée, il ne peut être obtenu et publié que dans la même structure de blocs synchronisés.

5.RentrantLock prend en charge le traitement des interruptions et ses performances sont meilleures que celles synchronisées.

Verrouillage en lecture-écriture ReentrantReadWriteLock (implémentation du verrouillage optimiste)

Un verrou en lecture-écriture maintient une paire de verrous, un verrou en lecture et un verrou en écriture. En séparant le verrou de lecture et le verrou d'écriture, la concurrence est grandement améliorée par rapport au verrou mutex général : plusieurs threads de lecture peuvent être autorisés à accéder en même temps, mais lorsque le thread d'écriture accède, tous les threads de lecture et d'écriture seront bloqués. .

Je suppose que tu aimes

Origine blog.csdn.net/weixin_54542328/article/details/133352056
conseillé
Classement