Code de blocage simulé:
public class LockLearn {
public static void main(String[] args) {
deadlock();
}
private static void deadlock()
{
Object lock1=new Object();
Object lock2=new Object();
//线程1 拥有 lock1 试图获取lock2
new Thread(()->{
synchronized (lock1){
System.out.println("获取 lock1 成功");
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(Thread.currentThread().getName());
}
}
}).start();
//线程2 拥有 lock2 试图获取lock1
new Thread(()->{
synchronized (lock2){
System.out.println("获取 lock2 成功");
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println(Thread.currentThread().getName());
}
}
}).start();
}
}
Lorsque le thread 1 a lock1, il essaie de posséder lock2 et le thread 2 a lock2 et essaie de posséder lock1, provoquant un blocage.
Serrure pessimiste
Les données adoptent une stratégie conservatrice pour la modification du monde extérieur
Il pense que les threads peuvent facilement modifier les données
Par conséquent, l'état verrouillé sera adopté pendant tout le processus de modification des données
Sachez qu'un thread est épuisé, d'autres threads peuvent continuer à utiliser
Code démo:
public class LockLearn2 {
public static void main(String[] args) {
synchronized (LockLearn2.class)
{
System.out.println("这是一个悲观锁的演示");
}
}
}
Compiler le code
// class version 52.0 (52)
// access flags 0x21
public class com/example/tangtang/boot/launch/JVM/LockLearn2 {
// compiled from: LockLearn2.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/example/tangtang/boot/launch/JVM/LockLearn2; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
// parameter args
TRYCATCHBLOCK L0 L1 L2 null
TRYCATCHBLOCK L2 L3 L2 null
L4
LINENUMBER 7 L4
LDC Lcom/example/tangtang/boot/launch/JVM/LockLearn2;.class
DUP
ASTORE 1
MONITORENTER
L0
LINENUMBER 9 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "\u8fd9\u662f\u4e00\u4e2a\u60b2\u89c2\u9501\u7684\u6f14\u793a"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
LINENUMBER 10 L5
ALOAD 1
MONITOREXIT
L1
GOTO L6
L2
FRAME FULL [[Ljava/lang/String; java/lang/Object] [java/lang/Throwable]
ASTORE 2
ALOAD 1
MONITOREXIT
L3
ALOAD 2
ATHROW
L6
LINENUMBER 11 L6
FRAME CHOP 1
RETURN
L7
LOCALVARIABLE args [Ljava/lang/String; L4 L7 0
MAXSTACK = 2
MAXLOCALS = 3
}
MONITORENTER est verrouillé
MONITOREXIT déverrouille, libère des ressources
Verrou optimiste
Dans des circonstances normales, il n'y aura pas de conflit lorsque les données sont modifiées
Aucun verrou avant l'accès aux données
Les données ne seront testées que lorsque les données seront soumises pour modifications
Le verrouillage optimiste en Java est principalement obtenu par des opérations de comparaison et d'échange (CAS) de comparaison et d'échange. CAS est une instruction atomique de synchronisation multithread. L'opération CAS contient trois informations importantes, à savoir l'emplacement de la mémoire, la valeur d'origine attendue et la nouvelle valeur . Si la valeur de l'emplacement mémoire est égale à la valeur d'origine attendue, alors la valeur de l'emplacement peut être mise à jour avec la nouvelle valeur, sinon aucune modification n'est apportée.
CAS peut causer des problèmes ABA. La question 0 ABA fait référence au thread qui obtient la valeur attendue d'origine A, mais lorsque CAS est sur le point d'être exécuté, d'autres threads préemptent l'exécution à droite, en changeant cette valeur de A à B, puis d'autres threads changent cette valeur de B à A. Cependant, la valeur de A à ce moment n'est plus la valeur d'origine de A, mais le thread d'origine ne connaît pas cette situation. Lorsqu'il exécute CAS, il compare uniquement La valeur d'origine devrait être modifié, ce qui provoque le problème ABA.
Prenons l'exemple d'un drame policier. Si quelqu'un met une boîte de 100 W en espèces à la maison, il l'utilisera pour le racheter dans quelques minutes. Cependant, lorsqu'il ne fait pas attention, un voleur entre et échange une boîte vide contre Après avoir laissé la boîte pleine d'argent, quand quelqu'un entre et voit que la boîte est exactement la même, il pensera que c'est la boîte d'origine et la prendra pour échanger la personne. Il doit y avoir un problème dans cette situation car la boîte est déjà vide Oui, c'est le problème d'ABA.
La méthode de gestion courante d'ABA consiste à ajouter le numéro de version et à mettre à jour le numéro de version après chaque modification. Prenons l'exemple ci-dessus, si chaque fois que la boîte est déplacée, la position de la boîte changera et cette position de changement équivaut à " numéro de version ", quand quelqu'un entre et constate que l'emplacement de la boîte a changé, il sait que quelqu'un a bougé ses mains et ses pieds et abandonnera le plan d'origine, résolvant ainsi le problème ABA.
La classe AtomicStampedReference fournie par JDK 1.5 peut également résoudre le problème ABA. Cette classe gère un tampon "numéro de version". Chaque fois qu'elle compare non seulement la valeur actuelle mais également le numéro de version, elle résout le problème ABA.
Le verrouillage optimiste présente un avantage: il est verrouillé au moment de la soumission, il ne provoquera donc pas de blocage.
Serrure réentrante
Verrou récursif - fait référence au même thread, si la fonction externe possède le verrou, la fonction interne peut continuer à acquérir le verrou
Synchronized et ReentrantLock en Java sont des verrous réentrants
Démonstration du code de verrouillage réentrant:
public class LockLearn3 {
public static void main(String[] args) {
reentrantA();
}
private synchronized static void reentrantA() {
System.out.println(Thread.currentThread().getName()+"执行reentrantA");
reentrantB();
}
private synchronized static void reentrantB() {
System.out.println(Thread.currentThread().getName()+"执行reentrantB");
}
}
On peut voir d'après les résultats que les threads d'exécution de la méthode reentrantA et de la méthode reentrantB sont tous les deux "main". Nous appelons la méthode reentrantA et sa méthode est imbriquée avec reentrantB. Si synchronized n'est pas réentrant, le thread sera bloqué tout le temps.
Le principe de réalisation des verrous réentrants est de stocker un identifiant de thread à l'intérieur du verrou pour déterminer à quel thread appartient le verrou actuel, et un compteur est maintenu à l'intérieur du verrou. Lorsque le verrou est inactif, la valeur de ce compteur est 0. Lorsque le verrou est le thread est occupé et réintégré, ajouter respectivement 1. Lorsque le verrou est libéré, le compteur est réduit de 1, jusqu'à ce qu'il soit réduit à 0, cela signifie que le verrou est inactif.
Serrure partagée et serrure pessimiste
Verrou exclusif signifie qu'au plus un thread peut maintenir le verrou à tout moment. Par exemple, synchronized est un verrou exclusif et ReadWriteLock verrou en lecture-écriture permet à plusieurs threads d'effectuer des opérations de lecture en même temps, ce qui est un verrou partagé.
Les verrous exclusifs peuvent être considérés comme des verrous pessimistes, et des verrous d'exclusion mutuelle doivent être ajoutés à chaque fois qu'une ressource est accédée, tandis que les verrous partagés peuvent être considérés comme des verrous optimistes, qui assouplissent les conditions de verrouillage et permettent à plusieurs threads d'accéder à la ressource en même temps. .