70-Quelles sont les stratégies pour résoudre le problème de l'impasse?

Que faire
si un blocage se produit en ligne Si un blocage se produit dans l'environnement en ligne, les conséquences néfastes ont effectivement été causées. Le meilleur moment pour résoudre le blocage est de "prévenir le problème avant qu'il ne survienne " , et non de le résoudre par la suite. Tout comme lors d'un incendie, une fois qu'il y a un gros incendie, il est quasiment impossible de l'éteindre sans causer de pertes. Le blocage est le même. Si un blocage se produit en ligne, afin de réduire la perte le plus rapidement possible, le meilleur moyen est de sauvegarder les données de la "scène de crise" telles que les informations et les journaux de la JVM, puis de redémarrer immédiatement le service. pour essayer de réparer l'impasse. Pourquoi dit-on que le redémarrage du service peut résoudre ce problème? Dans la mesure où il existe souvent de nombreuses conditions préalables à un blocage et lorsque la concurrence est suffisamment élevée, le blocage peut se produire, de sorte que la probabilité que le blocage se reproduise immédiatement après le redémarrage n'est pas très élevée . Lorsque nous redémarrons le serveur, vous pouvez assurer temporairement la disponibilité de services en ligne, puis utilisez les informations de la scène de crime que vous venez d'enregistrer pour résoudre les blocages, modifier le code et enfin republier.

Stratégies de réparation courantes

Quelles stratégies communes de réparation des blocages avons-nous? Trois stratégies de réparation principales seront présentées ci-dessous, à savoir:

  • Stratégie d'évitement
  • Stratégie de détection et de récupération
  • Stratégie d'autruche

Ils se concentrent sur différentes choses, commençons par des stratégies d'évitement.

Stratégie d'évitement

Comment éviter

L'idée principale de la stratégie d'évitement est d' optimiser la logique du code pour éliminer fondamentalement la possibilité de blocage . D'une manière générale, l'une des principales raisons du blocage est d'acquérir différents verrous dans l'ordre inverse. Nous montrons donc comment éviter les interblocages en ajustant la séquence d'acquisition des verrous .

Évitez les blocages lors du transfert de fonds

Jetons d'abord un coup d'œil à la situation de blocage lors du transfert. Cet exemple est un exemple schématique écrit pour en savoir plus sur les blocages, il est donc très différent de la conception d'un système bancaire réel, mais ce n'est pas grave, car nous cherchons principalement à éviter les blocages au lieu de transférer de l'argent. Logique commerciale.

(1) Un blocage s'est produit

Afin d'assurer la sécurité des threads, notre système de transfert doit obtenir deux verrous (deux objets de verrouillage) avant le transfert , à savoir le compte en cours de transfert et le compte en cours de transfert. Si ce niveau de restriction n'est pas imposé, pendant la période pendant laquelle un thread modifie l'équilibre, d'autres threads peuvent modifier la variable en même temps, ce qui peut entraîner des problèmes de sécurité des threads. Par conséquent, la balance ne peut pas être actionnée tant que les deux verrous n'ont pas été acquis; ce n'est qu'après l'acquisition des deux verrous que la prochaine opération de transfert réel peut être effectuée. Bien entendu, si le solde à transférer est supérieur au solde du compte, il ne peut pas être transféré, car le solde ne peut pas devenir un nombre négatif.

Pendant cette période, la possibilité de blocage est masquée. Jetons un œil au code:

public class TransferMoney implements Runnable {
    
    
    int flag;
    static Account a = new Account(500);
    static Account b = new Account(500);
    static class Account {
    
    
        public Account(int balance) {
    
    
            this.balance = balance;
        }
        int balance;
    }
    @Override
    public void run() {
    
    
        if (flag == 1) {
    
    
            transferMoney(a, b, 200);
        }
        if (flag == 0) {
    
    
            transferMoney(b, a, 200);
        }
    }
    public static void transferMoney(Account from, Account to, int amount) {
    
    
        //先获取两把锁,然后开始转账
        synchronized (to) {
    
    
            synchronized (from) {
    
    
                if (from.balance - amount < 0) {
    
    
                    System.out.println("余额不足,转账失败。");
                    return;
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println("成功转账" + amount + "元");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a的余额" + a.balance);
        System.out.println("b的余额" + b.balance);
        }
    }

Dans le code, l'indicateur de type int est d'abord défini, qui est un bit d'indicateur utilisé pour contrôler différents threads pour exécuter une logique différente. Ensuite, deux objets Compte, a et b, ont été créés pour représenter les comptes, et ils avaient initialement un solde de 500 yuans.

Examinons ensuite la méthode d'exécution, qui déterminera l'ordre des paramètres transmis à la méthode transferMoney en fonction de la valeur de l'indicateur. Si l'indicateur est 1, cela signifie que 200 yuans sont transférés du compte a au compte b; sur le au contraire, si le drapeau est 0, il transfère alors 200 yuans du compte b au compte a.

Regardons à nouveau la méthode de transfert transferMoney. Cette méthode va d'abord essayer d'acquérir deux verrous, à savoir synchronisé (à) et synchronisé (à partir de). Lorsque tout est obtenu avec succès, il déterminera d'abord si le solde est suffisant pour transférer le montant du transfert. S'il est insuffisant, il utilisera directement le retour pour retirer; si le solde est suffisant, il déduira le solde du transfert. sur le compte et déduisez le solde du compte transféré Ajoutez le solde à votre compte et imprimez enfin le message "Transfert réussi de XX yuans".

Dans la fonction principale, nous créons deux nouveaux objets TransferMoney et définissons leurs indicateurs sur 1 et 0 respectivement, puis nous les transmettons aux deux threads, les démarrons tous et enfin imprimons leurs soldes respectifs.

Les résultats de l'exécution sont les suivants:

成功转账200元
成功转账200元
a的余额500
b的余额500

Le code peut être exécuté normalement et le résultat d'impression est logique. Pour le moment, il n'y a pas de blocage, car le temps de maintien de chaque verrou est très court et la libération est également très rapide , donc dans le cas d'une faible concurrence, un blocage n'est pas facile à se produire. Ensuite, nous apportons quelques petits ajustements au code pour le bloquer.

Si nous ajoutons un Thread.sleep (500) entre les deux synchronisés pour simuler les retards du réseau bancaire, etc., alors la méthode transferMoney devient:

public static void transferMoney(Account from, Account to, int amount) {
    
    
    //先获取两把锁,然后开始转账
    synchronized (to) {
    
    
        try {
    
    
            Thread.sleep(500);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        synchronized (from) {
    
    
            if (from.balance - amount < 0) {
    
    
                System.out.println("余额不足,转账失败。");
                return;
            }
            from.balance -= amount;
            to.balance += amount;
            System.out.println("成功转账" + amount + "元");
        }
    }
}

On peut voir que le changement de transferMoney est qu'entre les deux synchronisés, c'est-à-dire après l'acquisition du premier verrou et avant l'acquisition du second verrou, nous avons ajouté une instruction qui dort pendant 500 millisecondes. Si vous exécutez à nouveau le programme à ce moment, il y aura une forte probabilité de blocage, qui entraînera aucune instruction à imprimer dans la console et le programme ne s'arrêtera pas.

Analysons pourquoi il se bloque. La raison principale est que deux threads différents acquièrent deux verrous dans l'ordre opposé (les deux comptes acquis par le premier thread et les deux comptes acquis par le second thread). L'ordre des comptes est exactement le contraire. Le «compte de transfert sortant» du premier thread est le «compte de transfert entrant» du deuxième thread ), nous pouvons donc résoudre le problème de blocage du point de vue de cet «ordre inverse».

(2) En fait, je ne me soucie pas de l'ordre dans lequel les serrures sont acquises

Après réflexion, nous pouvons constater qu'en fait, lors du transfert d'argent, nous ne nous soucions pas de l'ordre d'acquisition relatif des deux serrures. Lors du transfert de fonds, que nous obtenions d'abord l'objet de verrouillage de compte de transfert sortant ou l'objet de verrouillage de compte de transfert d'entrée, tant que nous pouvons enfin obtenir deux verrous, nous pouvons effectuer des opérations en toute sécurité. Ajustez donc l'ordre d'acquisition des verrous, de sorte que le compte acquis en premier n'ait rien à voir avec le fait que le compte soit "transféré" ou "transféré". Au lieu de cela , la valeur de HashCode est utilisée pour déterminer la séquence pour assurer la sécurité des threads .

La méthode transferMoney réparée est la suivante:

public static void transferMoney(Account from, Account to, int amount) {
    
    
    int fromHash = System.identityHashCode(from);
    int toHash = System.identityHashCode(to);
    if (fromHash < toHash) {
    
    
        synchronized (from) {
    
    
            synchronized (to) {
    
    
                if (from.balance - amount < 0) {
    
    
                    System.out.println("余额不足,转账失败。");
                    return;
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println("成功转账" + amount + "元");
            }
        }
    } else if (fromHash > toHash) {
    
    
        synchronized (to) {
    
    
            synchronized (from) {
    
    
                if (from.balance - amount < 0) {
    
    
                    System.out.println("余额不足,转账失败。");
                    return;
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println("成功转账" + amount + "元");
            }
        }
    }
}

Comme vous pouvez le voir, nous calculerons le HashCode de ces deux comptes séparément, puis déterminerons l'ordre d'acquisition des verrous en fonction de la taille du HashCode. De cette manière, quel que soit le thread qui s'exécute en premier, qu'il soit transféré ou transféré, l'ordre dans lequel il acquiert le verrou sera déterminé strictement en fonction de la valeur de HashCode, de sorte que l'ordre dans lequel tout le monde acquiert le verrou sera de même, et il n'y aura pas d'acquisition de verrouillage Dans l'ordre inverse , le blocage est évité.

(3) Il est plus sûr et plus pratique d'avoir une clé primaire

Jetons un coup d'œil à la façon d'utiliser la clé primaire pour déterminer la séquence d'acquisition du verrou, ce sera plus sûr et plus pratique. Tout à l'heure, nous avons utilisé HashCode comme norme de tri, car HashCode est plus courant et a tous les objets, mais il y a encore une très faible probabilité que le même HashCode se produise. En production réelle, c'est souvent une classe d'entité qui doit être triée, et une classe d'entité a généralement un ID de clé primaire. L' ID de clé primaire est unique et non répétitif , il est donc beaucoup plus pratique si notre classe contient une clé primaire. Il n'est pas nécessaire de calculer HashCode et d'utiliser directement son ID de clé primaire pour le tri. L'ordre d'acquisition des verrous est déterminé par la taille de l'ID de clé primaire, ce qui permet d'éviter les blocages.

Ci-dessus, nous avons introduit des stratégies pour éviter les blocages.

Stratégie de détection et de récupération

Regardons à nouveau la deuxième stratégie, qui est la stratégie de détection et de récupération.

Qu'est-ce qu'un algorithme de détection de blocage

Elle diffère de la stratégie précédente consistant à éviter les blocages. Pour éviter les blocages, il faut éviter les blocages via la logique. La stratégie de détection et de récupération consiste ici à permettre au système de se bloquer en premier, puis de le libérer . Par exemple, le système peut enregistrer les informations d'appel chaque fois que le verrou est appelé pour former un "graphe de lien d'appel de verrouillage", puis utiliser l'algorithme de détection de blocage pour le détecter à intervalles pour rechercher s'il y a une boucle dans ce graphe. Une fois qu'un blocage se produit, vous pouvez utiliser le mécanisme de récupération de blocage, tel que la privation d'une certaine ressource, pour déverrouiller le blocage et récupérer. Son idée est donc très différente de la précédente stratégie d'évitement des blocages.

Après avoir détecté le blocage, comment débloquer le blocage?

Terminaison de la méthode 1-thread

La première méthode pour déverrouiller le blocage consiste à mettre fin au thread (ou au processus, comme ci-dessous). Ici, le système mettra fin un par un aux threads qui sont tombés dans le blocage. Les threads sont arrêtés et les ressources sont libérées au niveau du en même temps, afin que le blocage soit résolu.

Bien sûr, cette résiliation doit faire attention à la commande, en général, il y a les considérations suivantes.

(1) Priorité

De manière générale, la priorité du thread ou du processus sera prise en compte lors de la fin, et les threads de faible priorité seront terminés en premier. Par exemple, le thread de premier plan sera impliqué dans l'affichage de l'interface, ce qui est très important pour l'utilisateur, de sorte que la priorité du thread de premier plan est souvent plus élevée que celle du thread d'arrière-plan.

(2) Ressources occupées et nécessaires

Dans le même temps, combien de ressources occupe un thread et combien de ressources sont encore nécessaires? Si un thread occupe déjà beaucoup de ressources et n'a besoin que des dernières ressources pour terminer la tâche avec succès, alors le système peut ne pas choisir de terminer un tel thread en premier, et choisira de terminer les autres threads pour donner la priorité à l'achèvement de la tâche. fil de discussion.

(3) Temps déjà en cours d'exécution

La durée d'exécution est un autre facteur à prendre en compte. Par exemple, le thread actuel est en cours d'exécution depuis de nombreuses heures, voire plusieurs jours, et la tâche sera bientôt terminée. Mettre fin à ce thread n'est peut-être pas un choix judicieux. Nous pouvons Laissez les threads qui viennent de commencer à s'exécuter se terminer et redémarrez-les plus tard, afin que le coût soit inférieur.

Il y aura une variété d'algorithmes et de stratégies, nous pouvons les ajuster en fonction de l'activité réelle.

Méthode 2 - Préemption des ressources

La deuxième façon de résoudre le blocage est la préemption des ressources. En fait, nous n'avons pas besoin de mettre fin à l'ensemble du thread, mais seulement de le priver des ressources qu'il a déjà acquises. Par exemple, laissez le thread revenir en arrière de quelques étapes et libérer des ressources, de sorte qu'il n'y ait pas besoin de terminer le thread entier. Cela aura des conséquences. Les conséquences de la fin du thread entier seront moindres et le coût sera inférieur .

Bien sûr, cette méthode a également un inconvénient, c'est-à-dire que si l'algorithme n'est pas bon, le thread que nous préemptons peut toujours être le même thread, ce qui entraînera une privation de thread . En d'autres termes, ce thread a été privé des ressources qu'il a déjà obtenues, alors il ne pourra plus fonctionner pendant longtemps.

Ce qui précède est la stratégie de détection et de récupération des interblocages.

Stratégie d'autruche

Examinons à nouveau la stratégie de l’autruche. La stratégie de l’autruche porte le nom de l’autruche, car l’autruche a pour particularité de se mettre la tête dans le sable lorsqu'elle rencontre un danger, de sorte qu’elle ne voit pas le danger.
Insérez la description de l'image ici
La stratégie de l'autruche signifie que si la probabilité de blocage dans notre système n'est pas élevée, et une fois que les conséquences ne sont pas particulièrement graves, nous pouvons choisir de l'ignorer d'abord. Il n'est pas impossible pour nous de le réparer manuellement jusqu'à ce que le blocage se produise, comme le redémarrage du service. Si notre système utilise moins de personnes, comme un système interne, il peut ne pas se bloquer pendant plusieurs années lorsque le niveau de concurrence est extrêmement faible . À cet égard, nous considérons le ratio entrées-sorties, et naturellement il n'y a pas lieu de traiter le problème de l'impasse: c'est un choix raisonnable basé sur nos scénarios d'affaires.

Résumé
Quelles sont les stratégies pour résoudre les blocages. Lorsqu'un blocage survient en ligne, le service en ligne doit être restauré d'abord après avoir sauvegardé des données importantes; puis trois stratégies de réparation spécifiques sont introduites: l'une est d'éviter la stratégie, l'idée principale est de changer l'ordre d'acquisition des verrous pour éviter le contraire. acquisition séquentielle de verrouillage; la seconde est la stratégie de détection et de récupération, qui permet à l'impasse de se produire, mais une fois qu'elle se produit, elle a une solution; la troisième est la stratégie de l'autruche.

Je suppose que tu aimes

Origine blog.csdn.net/Rinvay_Cui/article/details/111060319
conseillé
Classement