Maison  >  Article  >  Java  >  Introduction à plusieurs méthodes de synchronisation multithread Java

Introduction à plusieurs méthodes de synchronisation multithread Java

黄舟
黄舟original
2017-09-20 10:04:061493parcourir

Cet article présente principalement des informations pertinentes sur plusieurs méthodes de synchronisation des multi-threads Java. Voici 5 méthodes. Les amis dans le besoin peuvent se référer à

Plusieurs méthodes de synchronisation des multi-threads Java <.>

1. Introduction

Il y a quelques jours, lors de l'entretien, j'ai été torturé par le maître. à nouveau les connaissances de base. Sans plus attendre, entrons dans le vif du sujet.

2. Pourquoi la synchronisation des threads est nécessaire

Parce que lorsque nous avons plusieurs threads accédant à une variable ou un objet en même temps, s'il y a des lecteurs dans ceux-ci threads Lorsqu'il y a une autre opération d'écriture, la valeur de la variable ou l'état de l'objet sera confondu, entraînant une exception du programme. Par exemple, si un compte bancaire est géré par deux threads en même temps, l'un retire 100 yuans et l'autre dépose 100 yuans. Supposons que le compte ait à l'origine 0 bloc. Si le thread de retrait et le thread de dépôt se produisent en même temps, que se passera-t-il ? Le retrait d'argent échoue et le solde du compte est de 100. Le retrait d'argent est réussi et le solde du compte est de 0. Alors, lequel est-il ? C'est difficile à dire. Par conséquent, la synchronisation multithread doit résoudre ce problème.

3. Code lorsqu'il n'est pas synchronisé

Bank.java


package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count =0;//账户余额 

  //存钱 
  public void addMoney(int money){ 
    count +=money; 
    System.out.println(System.currentTimeMillis()+"存进:"+money); 
  } 

  //取钱 
  public void subMoney(int money){ 
    if(count-money < 0){ 
      System.out.println("余额不足"); 
      return; 
    } 
    count -=money; 
    System.out.println(+System.currentTimeMillis()+"取出:"+money); 
  } 

  //查询 
  public void lookMoney(){ 
    System.out.println("账户余额:"+count); 
  } 
}
SyncThreadTest . java


package threadTest; 
/**
 * Java学习交流QQ群:589809992 我们一起学Java!
 */
public class SyncThreadTest { 

  public static void main(String args[]){ 
    final Bank bank=new Bank(); 

    Thread tadd=new Thread(new Runnable() { 

      @Override 
      public void run() { 
        // TODO Auto-generated method stub 
        while(true){ 
          try { 
            Thread.sleep(1000); 
          } catch (InterruptedException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
          } 
          bank.addMoney(100); 
          bank.lookMoney(); 
          System.out.println("\n"); 

        } 
      } 
    }); 

    Thread tsub = new Thread(new Runnable() { 

      @Override 
      public void run() { 
        // TODO Auto-generated method stub 
        while(true){ 
          bank.subMoney(100); 
          bank.lookMoney(); 
          System.out.println("\n"); 
          try { 
            Thread.sleep(1000); 
          } catch (InterruptedException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
          }   
        } 
      } 
    }); 
    tsub.start(); 

    tadd.start(); 
  } 

}
Le code est très simple, donc je ne vais pas l'expliquer. Voyons à quoi ressemblent les résultats ? J'en ai coupé une partie. C'est très compliqué et je ne comprends pas.


余额不足 
账户余额:0 

余额不足 
账户余额:100 

1441790503354存进:100 
账户余额:100 

1441790504354存进:100 
账户余额:100 

1441790504354取出:100 
账户余额:100 

1441790505355存进:100 
账户余额:100 

1441790505355取出:100 
账户余额:100

4. Code lors de l'utilisation de la synchronisation

(1) Méthode de synchronisation :

Il existe une méthode pour modifier le mot-clé synchronisé. Étant donné que chaque objet en Java possède un verrou intégré, lorsqu'une méthode est modifiée avec ce mot-clé, le verrou intégré protégera l'intégralité de la méthode. Avant d'appeler cette méthode, vous devez obtenir le verrou intégré, sinon il sera bloqué.

Bank.java modifié


package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count =0;//账户余额 

  //存钱 
  public synchronized void addMoney(int money){ 
    count +=money; 
    System.out.println(System.currentTimeMillis()+"存进:"+money); 
  } 

  //取钱 
  public synchronized void subMoney(int money){ 
    if(count-money < 0){ 
      System.out.println("余额不足"); 
      return; 
    } 
    count -=money; 
    System.out.println(+System.currentTimeMillis()+"取出:"+money); 
  } 

  //查询 
  public void lookMoney(){ 
    System.out.println("账户余额:"+count); 
  } 
}
Regardez les résultats en cours :


余额不足 
账户余额:0 

余额不足 
账户余额:0 

1441790837380存进:100 
账户余额:100 

1441790838380取出:100 
账户余额:0 
1441790838380存进:100 
账户余额:100 

1441790839381取出:100 
账户余额:0
I j'ai l'impression de pouvoir le comprendre instantanément.


Remarque : le mot-clé synchronisé peut également modifier les méthodes statiques. Si la méthode statique est appelée à ce moment, la classe entière sera verrouillée

(2) Bloc de code synchronisé

C'est-à-dire le bloc d'instructions modifié par le mot-clé synchronisé. Le bloc de relevé modifié par ce mot-clé sera automatiquement ajouté avec un verrou intégré pour réaliser la synchronisation.

Le code Bank.java est le suivant :



Les résultats d'exécution sont les suivants :

package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count =0;//账户余额 

  //存钱 
  public  void addMoney(int money){ 

    synchronized (this) { 
      count +=money; 
    } 
    System.out.println(System.currentTimeMillis()+"存进:"+money); 
  } 

  //取钱 
  public  void subMoney(int money){ 

    synchronized (this) { 
      if(count-money < 0){ 
        System.out.println("余额不足"); 
        return; 
      } 
      count -=money; 
    } 
    System.out.println(+System.currentTimeMillis()+"取出:"+money); 
  } 

  //查询 
  public void lookMoney(){ 
    System.out.println("账户余额:"+count); 
  } 
}


L'effet est similaire à la première méthode.

余额不足 
账户余额:0 

1441791806699存进:100 
账户余额:100 

1441791806700取出:100 
账户余额:0 

1441791807699存进:100 
账户余额:100

Remarque : La synchronisation est une opération très coûteuse, le contenu synchronisé doit donc être minimisé. Habituellement, il n'est pas nécessaire de synchroniser l'intégralité de la méthode, utilisez simplement des blocs de code synchronisés pour synchroniser le code clé.

(3) Utilisez des variables de domaine spéciales (Volatile) pour réaliser la synchronisation des threads

a. Le mot-clé volatile fournit un mécanisme sans verrouillage pour accéder aux variables de domaine b. équivaut à Indiquer à la machine virtuelle que ce champ peut être mis à jour par d'autres threads. c Par conséquent, chaque fois que le champ est utilisé, il doit être recalculé au lieu d'utiliser la valeur dans le registre. Volatile ne fournit aucune opération atomique et ne peut pas non plus le faire. il peut être utilisé pour modifier les types finaux. Le code de la variable

Bank.java est le suivant :


Quel est l'effet de course. ?

package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private volatile int count = 0;// 账户余额 

  // 存钱 
  public void addMoney(int money) { 

    count += money; 
    System.out.println(System.currentTimeMillis() + "存进:" + money); 
  } 

  // 取钱 
  public void subMoney(int money) { 

    if (count - money < 0) { 
      System.out.println("余额不足"); 
      return; 
    } 
    count -= money; 
    System.out.println(+System.currentTimeMillis() + "取出:" + money); 
  } 

  // 查询 
  public void lookMoney() { 
    System.out.println("账户余额:" + count); 
  } 
}


Êtes-vous encore confus ? Pourquoi est-ce ? En effet, volatile ne peut pas garantir les opérations atomiques, donc volatile ne peut pas remplacer synchronisé. De plus, volatile empêchera le compilateur d'optimiser le code, donc si vous ne pouvez pas l'utiliser, ne l'appliquez pas. Son principe est que chaque fois qu'un thread veut accéder à une variable volatile modifiée, il la lit dans la mémoire au lieu de la lire dans le cache, donc la valeur de la variable accédée par chaque thread est la même. Cela garantit la synchronisation.

余额不足 
账户余额:0 

余额不足 
账户余额:100 

1441792010959存进:100 
账户余额:100 

1441792011960取出:100 
账户余额:0 

1441792011961存进:100 
账户余额:100
(4) Utilisez des verrous de réentrance pour réaliser la synchronisation des threads

Un nouveau package java.util.concurrent a été ajouté dans JavaSE5.0 pour prendre en charge la synchronisation. La classe ReentrantLock est un verrou réentrant et mutuellement exclusif qui implémente l'interface Lock. Il a le même comportement de base et la même sémantique que l'utilisation de méthodes et de blocs synchronisés, et étend ses capacités. Les méthodes couramment utilisées de la classe ReentrantLock sont : ReentrantLock() : Créer une instance ReentrantLock lock() : Obtenir le verrou unlock() : Libérer le verrou Remarque : ReentrantLock() possède également un constructeur qui peut créer un verrou équitable, mais parce qu'il peut réduire considérablement le programme Pour l'efficacité opérationnelle, il n'est pas recommandé d'utiliser la modification du code Bank.java comme suit :


Quel est l'effet de fonctionnement ?

package threadTest; 

import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private int count = 0;// 账户余额 

  //需要声明这个锁 
  private Lock lock = new ReentrantLock(); 

  // 存钱 
  public void addMoney(int money) { 
    lock.lock();//上锁 
    try{ 
    count += money; 
    System.out.println(System.currentTimeMillis() + "存进:" + money); 

    }finally{ 
      lock.unlock();//解锁 
    } 
  } 

  // 取钱 
  public void subMoney(int money) { 
    lock.lock(); 
    try{ 

    if (count - money < 0) { 
      System.out.println("余额不足"); 
      return; 
    } 
    count -= money; 
    System.out.println(+System.currentTimeMillis() + "取出:" + money); 
    }finally{ 
      lock.unlock(); 
    } 
  } 

  // 查询 
  public void lookMoney() { 
    System.out.println("账户余额:" + count); 
  } 
}


L'effet est similaire aux deux premières méthodes.

余额不足 
账户余额:0 

余额不足 
账户余额:0 

1441792891934存进:100 
账户余额:100 

1441792892935存进:100 
账户余额:200 

1441792892954取出:100 
账户余额:100

Si le mot-clé synchronisé peut répondre aux besoins de l'utilisateur, utilisez synchronisé car il peut simplifier le code. Si vous avez besoin de fonctions plus avancées, utilisez la classe ReentrantLock. À ce stade, faites attention à libérer le verrou à temps, sinon un blocage se produira généralement dans le code final

(5) Utilisation. variables locales pour réaliser la synchronisation des threads

Le code Bank.java est le suivant :


Effet d'exécution :

package threadTest; 

/** 
 * @author ww 
 * 
 */ 
public class Bank { 

  private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ 

    @Override 
    protected Integer initialValue() { 
      // TODO Auto-generated method stub 
      return 0; 
    } 

  }; 

  // 存钱 
  public void addMoney(int money) { 
    count.set(count.get()+money); 
    System.out.println(System.currentTimeMillis() + "存进:" + money); 

  } 

  // 取钱 
  public void subMoney(int money) { 
    if (count.get() - money < 0) { 
      System.out.println("余额不足"); 
      return; 
    } 
    count.set(count.get()- money); 
    System.out.println(+System.currentTimeMillis() + "取出:" + money); 
  } 

  // 查询 
  public void lookMoney() { 
    System.out.println("账户余额:" + count.get()); 
  } 
}


Écoutez, j'étais confus quant à l'effet de l'opération au début. Pourquoi est-il uniquement autorisé à enregistrer mais pas à retirer ? Jetez un œil au principe de ThreadLocal :

余额不足 
账户余额:0 

余额不足 
账户余额:0 

1441794247939存进:100 
账户余额:100 

余额不足 
1441794248940存进:100 
账户余额:0 

账户余额:200 

余额不足 
账户余额:0 

1441794249941存进:100 
账户余额:300

Si vous utilisez ThreadLocal pour gérer les variables, chaque thread qui utilise la variable obtiendra une copie de la variable. Les copies sont indépendantes les unes des autres, afin que chaque thread puisse modifier sa propre copie de la variable à volonté sans en provoquer. impact sur d’autres fils de discussion. Maintenant que vous comprenez, il s'avère que chaque thread exécute une copie, ce qui signifie que déposer de l'argent et retirer de l'argent sont deux comptes avec le même nom de connaissance. L’effet ci-dessus se produira donc.

ThreadLocal et mécanisme de synchronisation

a. ThreadLocal et le mécanisme de synchronisation servent tous deux à résoudre le problème des conflits d'accès aux mêmes variables dans les multi-threads b. méthode « espace contre temps », cette dernière adopte la méthode « d'échange de temps contre espace »

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:
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