Maison  >  Article  >  Java  >  Comment synchroniser Java

Comment synchroniser Java

爱喝马黛茶的安东尼
爱喝马黛茶的安东尼original
2019-11-12 17:59:203258parcourir

Comment synchroniser Java

Pourquoi la synchronisation est nécessaire

Java permet le contrôle de concurrence multithread lorsque plusieurs threads exploitent des variables de ressources partagées. (tels que l'ajout, la suppression, la modification et l'interrogation de données) entraîneront des données inexactes et des conflits les uns avec les autres. Par conséquent, un verrou de synchronisation est ajouté pour éviter d'être appelé par d'autres threads avant que le thread ne termine l'opération, garantissant ainsi que le thread. Unicité et précision des variables.

1. Exemple

Par exemple, si un compte bancaire est géré par deux threads en même temps, l'un prend 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 a échoué et le solde du compte est de 100. Le retrait a réussi et le solde du compte est de 0. Mais quel équilibre correspond à quoi ? C'est difficile à dire clairement, donc le problème de la synchronisation multithread se pose.

2. Situation sans synchronisation

Par exemple, si un compte bancaire est géré par deux threads en même temps, l'un prend 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 ? Si le retrait d’argent échoue, le solde du compte est de 100. Si le retrait d’argent réussit, le solde du compte est de 0. Mais quel équilibre correspond à quoi ? C'est difficile à dire clairement, donc le problème de la synchronisation multi-thread se pose.

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);
    }
}
package threadTest;
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();
}
}

Résultats d'exécution :

1502542307917取出:100
账号余额:100
1502542308917存进:100
1502542308917取出:100
账号余额:0
账号余额:0
1502542309917存进:100
账号余额:0
1502542309917取出:100
账号余额:0

Un problème de sécurité non lié aux threads se produit à ce moment-là, car deux threads accèdent à une méthode non synchronisée en même temps si ces deux threads exploitent des instances dans l'objet métier. en même temps, des problèmes de sécurité non liés aux threads peuvent survenir.

Solution : ajoutez simplement le mot-clé synchronisé devant public void run().

3. Méthode de synchronisation

Méthode de modification de mot-clé synchronisé

Il existe une méthode de modification de mot-clé synchronisée. É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é.

Code tel que :

public synchronized void save(){}

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

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);
}
}

Résultat d'exécution :

余额不足
账号余额:0
1502543814934存进:100
账号余额:100
1502543815934存进:100
账号余额:200
1502543815934取出:100
账号余额:100

La synchronisation des threads est obtenue de cette manière

Le bloc de code synchronisé

est modifié avec le synchronisé bloc d'instructions de mots-clés.

Le bloc d'instructions modifié par ce mot-clé sera automatiquement ajouté avec un verrou intégré pour réaliser la synchronisation.

Code tel que :

synchronized(object){ 
}

Remarque : la synchronisation est un fonctionnement de la méthode de coût, le contenu synchronisé doit donc être minimisé.

Habituellement, il n'est pas nécessaire de synchroniser l'ensemble de la méthode, utilisez simplement des blocs de code synchronisés pour synchroniser le code clé.

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);
}
}

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

余额不足
账户余额:0
余额不足
账户余额:100
1502544966411存进:100
账户余额:100
1502544967411存进:100
账户余额:100
1502544967411取出:100
账户余额:100
1502544968422取出:100

Cela permet également la synchronisation des threads, et l'efficacité opérationnelle est supérieure à celle de la synchronisation des méthodes. La synchronisation est une opération coûteuse, la synchronisation doit donc être minimisée. . contenu. 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é.

Utiliser des variables de domaine spéciales (volatiles) pour réaliser la synchronisation des threads

un mot-clé.volatile fournit un mécanisme sans verrouillage pour l'accès aux variables membres

b. Utiliser volatile pour modifier les variables membres équivaut à indiquer à la machine virtuelle que le champ peut être mis à jour par d'autres threads

c Par conséquent, chaque fois que la variable membre est utilisée, elle doit être recalculée à la place ; en utilisant la valeur dans le registre. Value;

d.volatile ne fournit aucune opération atomique et ne peut pas non plus être utilisé pour modifier les variables de type final.

Le code Bank.java est le suivant :

package com.thread.demo;
/**
* Created by HJS on 2017/8/12.
*/
public class Bank {
private volatile 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);
}
}

Résultat d'exécution :

余额不足
账户余额:0
余额不足
账户余额:100
1502546287474存进:100
账户余额:100
1502546288474存进:100
1502546288474取出:100
账户余额:100

À ce moment, la commande est à nouveau foirée, indiquant qu'il y a un autre problème de synchronisation, car volatile ne peut pas garantir les opérations atomiques. En raison de cela, 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.

Utilisation de 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 courantes de la classe ReenreantLock sont :

ReentrantLock() : Créer une instance ReentrantLock

lock() : Obtenir le verrou

unlock() : Release Lock

Remarque : ReentrantLock() a également une méthode de construction qui peut créer un verrou équitable, mais elle n'est pas recommandée car elle peut réduire considérablement l'efficacité de fonctionnement du programme.

Le code Bank.java est modifié comme suit :

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);
}
}

Résultats d'exécution :

余额不足
账户余额:0
1502547439892存进:100
账户余额:100
1502547440892存进:100
账户余额:200
1502547440892取出:100
账户余额:100

Remarque : concernant la sélection de l'objet Lock et du mot-clé synchronisé :

a. Il est préférable de n'utiliser aucun d'entre eux et d'utiliser un mécanisme fourni par le package java.util.concurrent pour aider les utilisateurs à gérer tout le code lié au verrouillage.

b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码。

c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。

使用局部变量实现线程同步

代码如下:

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());
}
}

运行结果如下:

复制代码
余额不足
账户余额:0
余额不足
1502547748383存进:100
账户余额:100
账户余额:0
余额不足
账户余额:0
1502547749383存进:100
账户余额:200

看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。

ThreadLocal 类的常用方法

ThreadLocal() : 创建一个线程本地变量     

get() : 返回此线程局部变量的当前线程副本中的值     

initialValue() : 返回此线程局部变量的当前线程的"初始值"     

set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

注:ThreadLocal与同步机制

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 

b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式。

php中文网,大量的免费Java入门教程,欢迎在线学习!

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
Article précédent:Comment sérialiser en JavaArticle suivant:Comment sérialiser en Java