Maison >Opération et maintenance >Sécurité >Comment analyser le verrouillage par double vérification en langage JAVA

Comment analyser le verrouillage par double vérification en langage JAVA

王林
王林avant
2023-05-12 08:55:171246parcourir

1. Verrouillage à double vérification

Dans le développement de programmes, il est parfois nécessaire de reporter certaines opérations coûteuses d'initialisation d'objets et de les initialiser uniquement lorsque ces objets sont utilisés. Dans ce cas, un verrouillage à double vérification peut être utilisé pour retarder l'objet. opérations d’initialisation. Le verrouillage à double vérification est un modèle de conception logicielle conçu pour réduire la concurrence et la surcharge de synchronisation dans les systèmes concurrents. Basé sur le modèle singleton ordinaire, il détermine d'abord si l'objet a été initialisé, puis décide de le verrouiller. Bien que le verrouillage à double vérification résolve les problèmes de threads dangereux et sujets aux erreurs des modèles singleton ordinaires dans les environnements multithread, il existe encore des dangers cachés. Ce qui suit prend le code source du langage JAVA comme exemple pour analyser les causes et les méthodes de réparation des défauts de verrouillage par revérification.

2. Les dangers du verrouillage à double vérification

Le verrouillage à double vérification n'a aucun impact dans un environnement monothread, car les threads changeront d'exécution à tout moment, dans ce cas. du réarrangement des instructions, l'objet L'instanciation n'est pas terminée, ce qui entraîne une erreur dans l'appel du programme.

3. Exemple de code

L'exemple provient de Samate Juliet Test Suite pour Java v1.3 (https://samate.nist.gov/SARD/testsuite.php), nom du fichier source : CWE609_Double_Checked_Locking__Servlet_01.java.

3.1 Code de défaut

Comment analyser le verrouillage par double vérification en langage JAVA

Les lignes de code ci-dessus sont les lignes 23 à 38. Le programme détermine d'abord si stringBad est nul, si non, puis renvoyez directement l'objet String, évitant ainsi d'avoir à saisir le synchronized bloque les ressources dépensées. Lorsque <code class="prettyprint code-in-text Prettyprinted">stringBad est nul, utilisez le mot-clé synchronized dans un fichier multithread. environnement Évitez de créer plusieurs fois des objets String. Lorsque le code est réellement exécuté, des erreurs peuvent toujours se produire dans le code ci-dessus. stringBad 是否为 null,如果不是则直接返回该 String 对象,这样避免了进入 synchronized 块所需要花费的资源。当 stringBad 为 null 时,使用 synchronized 关键字在多线程环境中避免多次创建 String 对象。在代码实际运行时,以上代码仍然可能发生错误。

对于第33行,创建 stringBad 对象和赋值操作是分两步执行的。但 JVM 不保证这两个操作的先后顺序。当指令重排序后,JVM 会先赋值指向了内存地址,然后再初始化 stringBad 对象。如果此时存在两个线程,两个线程同时进入了第27行。线程1首先进入了 synchronized 块,由于 stringBad 为 null,所以它执行了第33行。当 JVM 对指令进行了重排序,JVM 先分配了实例的空白内存,并赋值给 stringBad,但这时 stringBad 对象还未实例化,然后线程1离开了 synchronized 块。当线程2进入 synchronized 块时,由于 stringBad 此时不是 null ,直接返回了未被实例化的对象(仅有内存地址值,对象实际未初始化)。后续线程2调用程序对 stringBad

Pour la ligne 33, la création de l'objet stringBad et l'opération d'affectation s'effectuent en deux étapes. Mais la JVM ne garantit pas l'ordre de ces deux opérations. Lorsque les instructions sont réorganisées, la JVM attribuera d'abord la valeur pointant vers l'adresse mémoire, puis initialisera l'objet stringBad. S'il y a deux threads à ce moment-là, les deux threads entrent dans la ligne 27 en même temps. Le fil de discussion 1 est entré pour la première fois dans le bloc synchronisé. Puisque stringBad est nul, donc Il exécute la ligne 33. Lorsque la JVM réorganise les instructions, la JVM alloue d'abord la mémoire vierge de l'instance et l'assigne à stringBad, mais à ce moment stringBad n'a pas été instancié, puis le thread 1 a quitté le bloc synchronized. Lorsque le thread 2 entre dans le bloc synchronisé, car stringBad n'est pas nul à cette fois, renvoie directement l'objet non instancié (uniquement la valeur de l'adresse mémoire, l'objet n'est pas réellement initialisé). Lorsque le thread suivant 2 appelle le programme pour opérer sur l'objet stringBad, l'objet à ce moment n'a pas été initialisé, donc une erreur se produit.

Utilisez 360 ​​Code Guard pour détecter l'exemple de code ci-dessus. Vous pouvez détecter le défaut de "double contrôle de verrouillage" et le niveau d'affichage est moyen. Signalez le défaut à la ligne 27 du code, comme indiqué dans la Figure 1 :


Comment analyser le verrouillage par double vérification en langage JAVA

Figure 1 : Exemple de détection de "Double Check Lock"

3.2 Corriger le code

Comment analyser le verrouillage par double vérification en langage JAVA

🎜

Dans le code de réparation ci-dessus, utilisez le mot-clé volatile à la ligne 23 pour stringBad pour modification. <code class="prettyprint code-in-text Prettyprinted">volatile en tant que mot-clé d'instruction garantit que l'instruction ne sera pas omise en raison de l'optimisation du compilateur et nécessite une lecture directe de la valeur à chaque fois. volatile 关键字来对单例变量 stringBad 进行修饰。 volatile 作为指令关键字确保指令不会因编译器的优化而省略,且要求每次直接读值。

由于编译器优化,代码在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同,在单线程环境中并不会出错,然而一旦引入多线程环境,这种乱序就可能导致严重问题。 volatile 关键字就可以从语义上解决这个问题,值得关注的是 volatile 的禁止指令重排序优化功能在 Java 1.5 后才得以实现,因此1.5 前的版本仍然是不安全的,即使使用了 volatile

En raison de l'optimisation du compilateur, l'exécution réelle du code peut être différente de l'ordre que nous avons écrit. Le compilateur garantit uniquement que le résultat de l'exécution du programme est le même que le code source, mais ne garantit pas que l'ordre des instructions réelles est le même que le code source. Cependant, cela ne se passera pas mal dans un environnement monothread. une fois qu'un environnement multithread est introduit, ce type de désordre peut causer de graves problèmes. Le mot-clé volatile peut résoudre ce problème sémantiquement. Ce à quoi il convient de prêter attention est volatile<. la fonction d de r des instructions prohibitives> a été implémentée après Java 1.5, donc les versions antérieures à 1.5 sont toujours dangereuses, même si <code class="prettyprint code-in-text Prettyprinted">volatile Mots-clés.

Utilisez 360 ​​Code Guard pour détecter le code réparé, et vous pourrez constater que le défaut "double contrôle de verrouillage" n'existe plus. Comme le montre la figure 2 :


Comment analyser le verrouillage par double vérification en langage JAVA

Figure 2 : Résultats de détection après réparation

4. Comment éviter le verrouillage à double contrôle

Pour éviter le verrouillage à double contrôle, vous devez faire attention aux points suivants :

(1) Utiliser des mots clés volatiles pour éviter la réorganisation des instructions, mais cette solution nécessite JDK5 ou supérieur, car à partir de JDK5, il utilise la nouvelle spécification de modèle de mémoire JSR-133, qui améliore la sémantique de volatile.

(2) Solution basée sur l'initialisation de classe.

Comment analyser le verrouillage par double vérification en langage JAVA
La JVM effectuera l'initialisation de la classe pendant la phase d'initialisation de la classe (c'est-à-dire après le chargement de la classe et avant qu'elle ne soit utilisée par le thread). Lors de l'initialisation de la classe d'exécution, la JVM va acquérir un verrou. Ce verrou peut synchroniser l'initialisation d'une même classe par plusieurs threads.

🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer