Maison >Java >javaDidacticiel >Utilisation du mot-clé java synchronisé

Utilisation du mot-clé java synchronisé

高洛峰
高洛峰original
2017-01-05 17:12:511379parcourir

0. Code de question principale

Le code suivant montre un compteur, et deux threads effectuent une opération d'accumulation sur i en même temps, chacun s'exécutant 1 000 000 fois. Le résultat que nous attendons est définitivement i=2000000. Mais après l'avoir exécuté plusieurs fois, nous constaterons que la valeur de i est toujours inférieure à 2 000 000. En effet, lorsque deux threads écrivent sur i en même temps, le résultat d'un thread écrasera l'autre

<.>
public class AccountingSync implements Runnable {
  static int i = 0;
  public void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
  
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

Pour résoudre fondamentalement ce problème, nous devons nous assurer que plusieurs threads sont complètement synchronisés lorsqu'ils fonctionnent sur i. En d'autres termes, le thread A écrit sur i lors de la synchronisation. Le travail consiste à verrouiller le code synchronisé afin qu'un seul thread puisse entrer dans le bloc de synchronisation à la fois, assurant ainsi la sécurité entre les threads. Tout comme dans le code ci-dessus, l'opération de i ne peut être effectuée que simultanément. Un thread est en cours d'exécution.


2. Utilisation du mot-clé synchronisé

Spécifier le verrouillage de l'objet : verrouiller l'objet donné, saisir le bloc de code synchronisé pour obtenir l'objet donné Le verrou


agit directement sur la méthode instance : cela équivaut à verrouiller l'instance courante. La saisie du bloc de code de synchronisation nécessite l'obtention du verrou de l'instance courante (cela nécessite d'utiliser la même instance Runnable lors de la création du Thread)

Agit directement sur le statique. méthodes : équivalent au verrouillage de la classe actuelle. Avant d'entrer dans le bloc de code synchronisé, vous devez obtenir le verrou de la classe actuelle

2.1 Verrouiller l'objet spécifié

Le code suivant s'applique de manière synchronisée. à un objet donné. Une chose à noter ici est que l'objet donné doit être statique, sinon, chaque fois que nous créons un nouveau thread, l'objet ne sera pas partagé entre eux et la signification du verrouillage est également différente. n'existe plus.


public class AccountingSync implements Runnable {
  final static Object OBJECT = new Object();
  
  static int i = 0;
  public void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (OBJECT) {
        increase();
      }
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

2.2 Agir directement sur les méthodes d'instance

Le mot-clé synchronisé agit sur les méthodes d'instance, ce qui signifie qu'avant de saisir la méthode Increase(), le thread doit obtenir le verrou de l'instance actuelle. Cela nous oblige à utiliser la même instance d'objet Runnable lors de la création d'une instance de Thread. les verrous des threads ne sont pas sur la même instance, et il n'y a aucun moyen de parler de problèmes de verrouillage/synchronisation

Trois lignes, expliquant l'utilisation correcte des mots-clés sur les méthodes d'instance
<.>

2.3 Agir directement sur les méthodes statiques

Utilisez le mot-clé synchronisé sur les méthodes statiques, il n'est donc pas nécessaire de faire ce qui précède Dans l'exemple, deux threads doivent pointer vers la même méthode Runnable Parce que. le bloc de méthode doit demander le verrouillage de la classe actuelle, pas de l'instance actuelle, les threads peuvent toujours être synchronisés correctement
public class AccountingSync implements Runnable {
  static int i = 0;
  public synchronized void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
  
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}


3. verrouillage

De l'exemple ci-dessus, nous savons que si nous avons besoin d'une application de compteur, afin de garantir l'exactitude des données, nous aurons naturellement besoin de Le compteur est verrouillé, nous pouvons donc écrire le code suivant :

public class AccountingSync implements Runnable {
  static int i = 0;
  public static synchronized void increase() {
    i++;
  }
  
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

Lorsque nous exécutons le code ci-dessus, nous constaterons que la sortie i est très petite. Cela montre que les threads ne sont pas sûrs

.

Pour expliquer ce problème, il faut commencer par Integer : En Java, Integer est un objet immuable. Comme String, une fois l'objet créé, il ne peut pas être modifié. Si vous avez un Integer=1, alors il le sera. soit toujours 1. Et si vous voulez que cet objet = 2 ? Vous ne pouvez recréer qu'un Integer. Après chaque i, cela équivaut à appeler la méthode valueOf d'Integer.

public class BadLockOnInteger implements Runnable {
  static Integer i = 0;
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (i) {
        i++;
      }
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    BadLockOnInteger badLockOnInteger = new BadLockOnInteger();
  
    Thread t1 = new Thread(badLockOnInteger);
    Thread t2 = new Thread(badLockOnInteger);
  
    t1.start();
    t2.start();
  
    t1.join();
    t2.join();
  
    System.out.println(i);
  }
}

Integer.valueOf() est en fait une méthode d'usine, et elle aura tendance à renvoyer un nouvel objet Integer et à copier à nouveau la valeur dans i ;

Nous connaissons donc la cause du problème. Parce qu'entre plusieurs threads, après i, i pointe vers un nouvel objet. Par conséquent, le thread peut charger une instance d'objet différente à chaque fois qu'il se verrouille. La solution est très simple, utilisez simplement l'une des trois méthodes de synchronisation ci-dessus

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}

Pour plus d'articles liés à l'utilisation du mot-clé java synchronisé, veuillez faire attention au site Web PHP chinois. !


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn