Maison >Opération et maintenance >Sécurité >Comment analyser le verrouillage par double vérification en langage JAVA
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.
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.
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.
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
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 : 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
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 : (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.
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!