Maison  >  Article  >  Java  >  Comment utiliser le verrouillage optimiste CAS et Java

Comment utiliser le verrouillage optimiste CAS et Java

王林
王林avant
2023-05-01 20:07:161046parcourir

Qu'est-ce que CAS

CAS est CompareAndSwap, ce qui signifie comparaison et échange. Pourquoi CAS n'utilise-t-il pas de verrous mais assure-t-il néanmoins une manipulation sûre des données dans des conditions concurrentes ? Le nom montre en fait le principe de CAS de manière très intuitive. Le processus spécifique de modification des données est le suivant :

  1. Lorsque vous utilisez CAS pour exploiter des données, la valeur d'origine. des données et La valeur à modifier est également transmise à la méthode

  2. Comparez si la valeur actuelle de la variable cible est la même que la valeur d'origine transmise

  3. Si elles sont identiques, cela signifie que la valeur cible la variable n'a pas été modifiée par d'autres threads, modifiez simplement la valeur de la variable cible directement

  4. Si la valeur de la variable cible est différente de la valeur d'origine, cela prouve que la variable cible a été modifiée par d'autres threads, et cette modification CAS a échoué

D'après le processus ci-dessus, nous pouvons voir que CAS garantit en fait une modification sûre des données, mais la modification Il existe une possibilité d'échec, c'est-à-dire que la modification des données de la variable cible échoue à ce stade, nous devons boucler. pour déterminer le résultat de la modification des données par CAS, et réessayez en cas d'échec.

Les étudiants qui réfléchissent plus attentivement peuvent craindre que l'opération de comparaison et de remplacement du CAS lui-même entraîne des problèmes de sécurité de concurrence. Dans les applications réelles, cette situation ne se produira pas par JDK à l'aide du CAS au niveau matériel. primitives pour garantir que la comparaison et le remplacement sont une action atomique.

CAS implémente une programmation sans verrouillage

La programmation sans verrouillage fait référence au fonctionnement sûr des variables partagées sans utiliser de verrousDans la programmation simultanée, nous utilisons divers verrous pour assurer la sécurité des variables partagées. Autrement dit, il est garanti que les autres threads ne pourront pas exploiter la même variable partagée lorsqu'un thread n'a pas fini d'exploiter la variable partagée.
Une utilisation correcte des verrous peut garantir la sécurité des données en cas de concurrence, mais lorsque le degré de concurrence n'est pas élevé et que la concurrence n'est pas féroce, l'acquisition et la libération de verrous deviennent un gaspillage de performances inutile. Dans ce cas, vous pouvez envisager d'utiliser CAS pour garantir la sécurité des données et obtenir une programmation sans verrouillage.

Le problème gênant de l'ABA

Nous avons déjà compris le principe du CAS pour garantir un fonctionnement sûr des variables partagées, mais le fonctionnement du CAS ci-dessus reste le cas. a des défauts. Supposons que la valeur de la variable partagée accédée par le thread actuel est A. Pendant le processus par lequel le thread 1 accède à la variable partagée, le thread 2 exploite la variable partagée et l'assigne à B. Une fois que le thread 2 a traité sa propre logique, il attribue le variable partagée avec A. À ce stade, le thread 1 compare la valeur de la variable partagée A avec la valeur d'origine A, croit à tort qu'aucun autre thread n'exploite la variable partagée et renvoie directement le succès de l'opération. C’est le problème de l’ABA. Bien que la plupart des entreprises n'aient pas besoin de se soucier de savoir si d'autres modifications ont été apportées aux variables partagées, tant que la valeur d'origine est cohérente avec la valeur actuelle, le résultat correct peut être obtenu. Cependant, il existe certains scénarios sensibles dans lesquels ce n'est pas seulement le cas. Le résultat de la variable partagée équivaut à ne pas être modifié, mais il n'est pas non plus acceptable que les variables partagées soient modifiées par d'autres threads dans le processus. Heureusement, il existe une solution mature au problème ABA. Nous ajoutons un numéro de version à la variable partagée, et la valeur du numéro de version augmentera automatiquement à chaque fois que la variable partagée est modifiée. Dans l'opération CAS, ce que nous comparons n'est pas la valeur d'origine de la variable, mais le numéro de version de la variable partagée. Le numéro de version mis à jour pour chaque opération de variable partagée est unique, ce qui permet d'éviter le problème ABA.

Scénarios d'application spécifiques

Application CAS dans JDK

Tout d'abord, il est dangereux que plusieurs threads effectuent des opérations simultanées sur des variables ordinaires. Le résultat de l'opération d'un thread peut être écrasé par d'autres threads. Par exemple, nous utilisons maintenant. deux threads, chacun Chaque thread augmente la variable partagée avec une valeur initiale de 1 de un. S'il n'y a pas de mécanisme de synchronisation, le résultat de la variable partagée est probablement inférieur à 3. Autrement dit, il est possible que le thread 1 et le thread 2 aient lu la valeur initiale 1, que le thread 1 l'attribue à 2 et que la valeur lue par le thread 2 dans la mémoire où il se trouve reste inchangée. par 1 et l'attribue à 2, donc le résultat final est 2, ce qui est inférieur au résultat attendu de 3. L'opération d'incrémentation n'est pas une opération atomique, ce qui conduit au problème dangereux du fonctionnement des variables partagées. Afin de résoudre ce problème, JDK fournit une série de classes atomiques pour fournir les opérations atomiques correspondantes. Voici le code source de la méthode getAndIncrement dans AtomicInteger. Examinons le code source pour voir comment utiliser CAS pour implémenter l'addition atomique thread-safe de variables entières.

<code>/**<br> * 原子性的将当前值增加1<br> *<br> * @return 返回自增前的值<br> */<br>public final int getAndIncrement() {<br>    return unsafe.getAndAddInt(this, valueOffset, 1);<br>}<br></code>

Vous pouvez voir que getAndIncrement appelle en fait la méthode getAndAddInt de la classe UnSafe pour implémenter des opérations atomiques. Voici le code source getAndAddInt

<code>/**<br> * 原子的将给定值与目标字变量相加并重新赋值给目标变量<br> *<br> * @param o 要更新的变量所在的对象<br> * @param offset 变量字段的内存偏移值<br> * @param delta 要增加的数字值<br> * @return 更改前的原始值<br> * @since 1.8<br> */<br>public final int getAndAddInt(Object o, long offset, int delta) {<br>    int v;<br>    do {<br>    	// 获取当前目标目标变量值<br>        v = getIntVolatile(o, offset);<br>    // 这句代码是关键, 自旋保证相加操作一定成功<br>    // 如果不成功继续运行上一句代码, 获取被其他<br>    // 线程抢先修改的变量值, 在新值基础上尝试相加<br>    // 操作, 保证了相加操作的原子性<br>    } while (!compareAndSwapInt(o, offset, v, v + delta));<br>    return v;<br>}<br></code>
.

Nous connaissons tous les verrous, tels que les verrous réentrants. Divers verrous fournis par JDK reposent essentiellement sur la classe AbstractQueuedSynchronizer Lorsque plusieurs threads tentent d'acquérir des verrous, ils entrent dans une file d'attente pour attendre, et l'opération de file d'attente multithread est atomique. .La fiabilité est garantie par CAS.Le code source est le suivant :

<code>/**<br> * 锁底层等待获取锁的线程入队操作<br> * @param node 要入队的线程节点<br> * @return 入队节点的前驱节点<br> */<br>private Node enq(final Node node) {<br>// 自旋等待节点入队, 通过cas保证并发情况下node安全正确入队<br>    for (;;) {<br>        Node t = tail;<br>        // head为空时构造dummy node初始化head和tail<br>        if (t == null) {<br>            if (compareAndSetHead(new Node()))<br>                tail = head;<br>        } else {<br>            node.prev = t;<br>            // 如果cas设置tail失败了<br>            // 下个循环取到了最新的其他线程抢先设置的tail<br>            // 继续尝试设置.<br>            if (compareAndSetTail(t, node)) {<br>                t.next = node;<br>                return t;<br>            }<br>        }<br>    }<br>}<br>/**<br> * 原子性的设置tail尾节点为新入队的节点<br> */<br>private final boolean compareAndSetTail(Node expect, Node update) {<br>// 可以看到此处又是调用了Unsafe类下的原子操作方法<br>// 如果目标字段(tail尾节点字段)当前值是预期值<br>// 即没有被其他线程抢先修改成功, 那么就设置成功<br>// 返回true<br>    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);<br>}</code>  
  

Application de verrouillage optimiste dans le développement d'entreprise

En plus des diverses opérations atomiques fournies par la classe Uusafe dans le JDK, nous pouvons utiliser les idées CAS pour assurer la concurrence. en développement actuel Exploiter les bases de données en toute sécurité. Supposons que la structure et les données de la table utilisateur sont les suivantes. Le champ de version est la clé pour implémenter le verrouillage optimiste

假设我们有一个用户领取优惠券的按钮,怎么防止用户快速点击按钮造成重复领取优惠券的情况呢。我们要安全的更改id为1的用户的coupon_num优惠券数量,将version字段作为CAS比较的版本号,即可避免重复增加优惠券数量,比较和替换这个逻辑通过WHERE条件来实现. 涉及sql如下:

<code>UPDATE user <br>SET coupon_num = coupon_num + 1, version = version + 1 <br>WHERE version = 0</code>

可以看到,我们查询出id为1的数据, 版本号为0,修改数据的同时把当前版本号当做条件即可实现安全修改,如果修改失败,证明已经被其他线程修改过,然后看具体业务决定是否需要自旋尝试再次修改。这里要注意考虑竞争激烈的情况下多个线程自旋导致过度的性能消耗,根据并发量选择适合自己业务的方式

1 Zhu Xiaoming. 0 0

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