synchronisé et verrouillage du moniteur

1. verrouillage synchronisé et moniteur

1.1 Introduction à la synchronisation

En Java, synchronized est un mot-clé utilisé pour implémenter la synchronisation des threads et le contrôle d'exclusion mutuelle. Il peut décorer des méthodes ou des blocs de code pour protéger l'accès aux ressources partagées et éviter les problèmes de concurrence causés par plusieurs threads modifiant des données en même temps.

Plus précisément, le mot clé synchronized fonctionne comme suit :

  1. Exclusion mutuelle : Le mot clé synchronized garantit qu'un seul thread peut exécuter la méthode ou le bloc de code modifié par synchronized en même temps. Lorsqu'un thread entre dans un bloc de code synchronisé, il essaie d'acquérir le verrou du moniteur de l'objet (verrou interne). Si le verrou est déjà occupé par d'autres threads, le thread actuel sera bloqué jusqu'à ce que le verrou soit acquis pour s'exécuter, garantissant ainsi l'accès exclusif mutuel de plusieurs threads aux ressources partagées.

  2. Visibilité : le mot clé synchronized fournit non seulement une exclusion mutuelle, mais garantit également que les modifications apportées par un thread à une variable partagée avant de libérer le verrou sont visibles pour les autres threads. Cela signifie que lorsqu'un thread modifie une variable partagée protégée par synchronized, les autres threads peuvent voir la dernière valeur après avoir acquis le verrou, évitant ainsi le problème d'incohérence des données.

L'utilisation du mot clé synchronized peut prévenir efficacement les problèmes de simultanéité tels que la concurrence des données et les conditions de concurrence causées par plusieurs threads accédant aux ressources partagées en même temps. C'est l'un des mécanismes de synchronisation les plus couramment utilisés en Java.

Lors de l'utilisation synchronisée, vous devez faire attention aux points suivants :

  1. Les blocs de code qui peuvent protéger les ressources partagées doivent être aussi étroits que possible pour éviter une concurrence de verrouillage inutile.

  2. Le mot clé synchronized peut être utilisé sur les méthodes d'instance, les méthodes statiques et les blocs de code. Pour les méthodes d'instance et les blocs de code, l'objet lock est l'instance actuelle ; pour les méthodes statiques et les blocs de code, l'objet lock est l'objet Class de la classe actuelle.

  3. Si plusieurs threads accèdent à différentes instances d'objet, les verrous entre eux ne s'excluent pas mutuellement. En d'autres termes, la synchronisation isole l'accès simultané entre différentes instances d'objet.

  4. Dans certains cas, l'utilisation de l'interface Lock et de ses classes d'implémentation (telles que ReentrantLock) peut fournir un contrôle de verrouillage plus flexible et plus précis, mais le verrou doit être libéré manuellement.

En bref, le mot clé synchronized est un outil important en Java pour la synchronisation des threads et le contrôle de l'exclusion mutuelle. En garantissant l'exclusivité et la visibilité des sections critiques, il protège efficacement les ressources partagées et améliore la sécurité et l'exactitude des programmes multi-threads. .

1.2 Verrouillage du moniteur (le verrouillage du moniteur est également appelé verrouillage interne ou mutex)

Lors de l'utilisation de mots clés synchronized, le concept de verrous est impliqué. Pour synchronizedles blocs de code ou les méthodes, les verrous sont un mécanisme utilisé pour protéger les ressources partagées.

En Java, chaque objet est associé à un verrou de moniteur (également appelé verrou intrinsèque ou mutex). Lorsqu'un thread souhaite entrer dans un synchronizedbloc de code ou une méthode modifiés, il doit d'abord acquérir le verrou de l'objet avant d'exécuter le bloc de code ou la méthode.

Object object2 = new Object();

synchronized (object2) {
    // 同步代码块
    // 这里的锁就是对象object2
    // 只有获得object2的锁才能执行这段同步代码块
}

Dans le code ci-dessus, un object2objet nommé est utilisé comme objet de verrouillage. Ce n'est que lorsque le verrou est acquis object2, c'est-à-dire que le verrouillage du moniteur de l'objet est acquis avec succès , que le contenu du bloc de code peut object2être exécuté . synchronizedLes autres object2threads tentant d'acquérir le verrou sont bloqués jusqu'à ce que le verrou soit libéré.

Par conséquent, le code de ce fragment de code synchronized (object2)signifie qu'un seul thread peut entrer dans le bloc de code qui lui object2est associé en même temps synchronizedet que les autres threads doivent attendre que le verrou soit libéré avant de continuer à s'exécuter.

Notez que l'objet verrou peut être n'importe quel objet, "object2" ici n'est qu'un exemple de nom de variable. Il est important de partager le même objet de verrouillage entre plusieurs threads pour obtenir une synchronisation des threads et un contrôle d'exclusion mutuelle.

2. Sécurité des fils


2.1 Démonstration de la sécurité des threads


Si plusieurs threads accèdent conjointement aux variables membres (partagées) et les modifient, il y aura des problèmes de sécurité des threads.

Comme suit : Il y a 20 beans sur la table, chaque thread prend 1 bean sur la table et le nombre de beans restants est affiché sur la table.

Lorsque le nombre de beans restant sur la table est 0, une exception est levée.

package day06.threadDemo;
 
public class Demo6 {
    public static void main(String[] args) {
        Table table = new Table();
        Table.Person p1 = table.new Person();
        Table.Person p2 = table.new Person();
        p1.start();
        p2.start();
 
    }
 
}
 
class Table{
    int beans = 20;
    public int getBeans(){
        if(beans ==0){
            throw new RuntimeException("豆没了");
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return beans--;
    }
 
    class Person extends Thread{
        @Override
        public void run() {
            while (true){
                int beans = getBeans();
                System.out.println(this.getName()+":"+beans);
            }
        }
    }
}

Résultats du :

 Lorsque les haricots restants sur la table ne sont pas 0, allez à la table pour obtenir les haricots.

Lorsqu'il reste 1 bean sur la table, le thread 0 et le thread 1 iront sur la table pour récupérer le bean en même temps (il y a un problème de probabilité ici, nous avons ouvert deux threads, ils ne récupèrent pas forcément le dernier bean en même temps. Si ce n'est pas en même temps, il n'y aura pas de problèmes de sécurité des threads. Si deux threads prennent le dernier en même temps), le thread 0 prend le dernier bean, et le thread 1 en prend également un, puis la table sera Il y a -1. 

2.2 Résolution des problèmes de sécurité des threads


1. N'accédez pas aux variables communes si vous ne pouvez pas y accéder et ne les modifiez pas si vous le pouvez.

2. Verrouillage (mais cela affectera les performances)

public synchronisé int getBeans()

Verrouillez la méthode getBeans.

Après qu'un thread entre dans cette méthode, les autres threads ne peuvent pas entrer. Une fois que le thread a exécuté le programme, le verrou est libéré et d'autres threads peuvent utiliser ce programme (pour un exemple dégoûtant, plusieurs personnes vont aux toilettes et entrent dans les toilettes). Après personnes, d'autres feront la queue)

2.3 Verrouillage de la méthode


Ajouter le mot clé synchronized à la méthode

    public synchronized int  getBeans(){
        if(beans ==0){
            throw new RuntimeException("豆没了");
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return beans--;
    }


Équivalent à

    public int getBean(){
        synchronized (this){
            if(beans ==0){
                throw new RuntimeException("豆没了");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return beans--;
        }
        }

 Dans synchronized (), pour exécuter la ressource (en particulier le verrou de l'objet), il s'agit de son propre objet.
Verrouillez toute la méthode.

Code général :

Voici la ressource qui utilise son propre objet Table comme verrou.

package com.example.analyzestack.controller;



public class SynchronizedTest {
    public static void main(String[] args) {
        Table table = new Table();
        Table.Person p1= table.new Person();
        Table.Person p2 = table.new Person();
        p1.start();
        p2.start();
    }

}


class Table{
    int beans =20;
    public synchronized int getBeans() {
        // synchronized  加锁
        if(beans==0){
            throw new RuntimeException("豆子没有了");
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return beans--;
    }

    class Person extends Thread{
        @Override
        public void run() {
            while (true){
                int beans =getBeans();
                System.out.println(this.getName()+":"+beans);
            }
        }
    }
}

2.4 Verrouillage de la méthode statique


Méthode statique, la valeur par défaut consiste à utiliser la classe de la classe comme verrou

    // 静态方法,默认使用的是类的class当锁(Table.class)
    public static synchronized void test1(){
        synchronized (Table.class){
            
        }
    }


2.5 Verrouillage invalide


Lorsque nous verrouillons, nous verrouillons le même objet. comme cette situation

    public synchronized int  getBeans(){
        Object obj = new Object();
        synchronized (obj){
            if(beans ==0){
                throw new RuntimeException("豆没了");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return beans--;
 
        }


Verrouiller obj, obj n'est pas un objet de notre classe, et un obj sera créé chaque fois que la méthode getBeans sera appelée. Ce verrou ne sert à rien.

2.6 Blocage


Deux threads, le thread 1 occupe le verrou A et va au verrou B. Le thread 2 occupe le verrou B et va au verrou A. L'autre ne peut pas obtenir de ressources, ce qui entraîne une impasse.

Par exemple

package day06.threadDemo;
 
public class Demo8 {
    public static void main(String[] args) {
        Boo boo = new Boo();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                boo.test1();
            }
        };
 
        Thread t2 = new Thread(){
            @Override
            public void run() {
                boo.test2();
            }
        };
        t1.start();
        t2.start();
 
    }
}
 
class Boo{
    private Object object1 = new Object();
    private Object object2 = new Object();
 
    public void test1(){
        synchronized (object1){
            System.out.println(Thread.currentThread().getName()+"-obj1");
            synchronized (object2){
                System.out.println(Thread.currentThread().getName()+"-obj2");
            }
        }
    }
 
    public void test2(){
        synchronized (object2){
            System.out.println(Thread.currentThread().getName()+"-obj2");
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+"-obj1");
            }
        }
    }
}

En exécutant ce programme, le programme a été exécuté pour toujours. A ce stade, c'est dans l'impasse.

Je suppose que tu aimes

Origine blog.csdn.net/qq_39208536/article/details/131472319
conseillé
Classement