Maison Java Javacommencer Comment synchroniser Java

Comment synchroniser Java

Nov 12, 2019 pm 05:59 PM
java synchrone

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 de ce site Web
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

Outils d'IA chauds

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Tutoriel PHP
1535
276
Excel trouver et remplacer ne fonctionne pas Excel trouver et remplacer ne fonctionne pas Aug 13, 2025 pm 04:49 PM

CheckkSearchSettings like "MatchEnteRireCellContents" et "MatchCase" ByExpandingOptionsInFindanDreplace, garantissant "lookin" issettominuesand »dans" TOCORRECTSCOPE; 2.LOORHFORHIDDENCHARACTER

Comment déployer une application Java Comment déployer une application Java Aug 17, 2025 am 12:56 AM

Préparez-vous en application par rapport à Mavenorgradletobuildajarorwarfile, externalisationConfiguration.2.ChoOSEADPLOYENDIRONMENT: Runonbaremetal / vmwithjava-jarandsystemd, deploywarontomcat, compeneriserisewithdocker, orusecloudplatformslikelise.

Comment configurer la journalisation dans une application Java? Comment configurer la journalisation dans une application Java? Aug 15, 2025 am 11:50 AM

L'utilisation de SLF4J combinée avec la journalisation ou le log4j2 est le moyen recommandé de configurer les journaux dans les applications Java. Il introduit des bibliothèques API et implémentation en ajoutant des dépendances Maven correspondantes; 2. Obtenez l'enregistreur via le loggerfactory de SLF4J dans le code et écrivez le code journal découplé et efficace à l'aide de méthodes de journalisation paramétrée; 3. Définir le format de sortie du journal, le niveau, la cible (console, le fichier) et le contrôle du journal du package via Logback.xml ou les fichiers de configuration log4j2.xml; 4. Activer éventuellement la fonction de balayage de fichiers de configuration pour atteindre un ajustement dynamique du niveau de journal, et Springboot peut également être géré via des points de terminaison de l'actionneur; 5. Suivez les meilleures pratiques, y compris

Liaison des données XML avec Castor en Java Liaison des données XML avec Castor en Java Aug 15, 2025 am 03:43 AM

CASTORENablesxml-to-javaObjectMappingViadefaultConverionsOrexplicitMappingFiles; 1) DefinejavaclasseswithGetters / seters; 2) useUnmarShallertOConvertXmltoObjects; 3)

JS Ajouter un élément au début du tableau JS Ajouter un élément au début du tableau Aug 14, 2025 am 11:51 AM

Dans JavaScript, la méthode la plus courante pour ajouter des éléments au début d'un tableau est d'utiliser la méthode Unsich (); 1. En utilisant unsith () modifiera directement le tableau d'origine, vous pouvez ajouter un ou plusieurs éléments pour retourner la nouvelle longueur du tableau ajouté; 2. Si vous ne souhaitez pas modifier le tableau d'origine, il est recommandé d'utiliser l'opérateur d'extension (tel que [Newelement, ... Arr]) pour créer un nouveau tableau; 3. Vous pouvez également utiliser la méthode CONCAT () pour combiner le nouveau tableau d'éléments avec le numéro d'origine, renvoyez le nouveau tableau sans modifier le tableau d'origine; En résumé, utilisez Unsich () lors de la modification du tableau d'origine et recommandez l'opérateur d'extension lorsque vous gardez le tableau d'origine inchangé.

Comparaison des performances: Java Vs. GO pour les services backend Comparaison des performances: Java Vs. GO pour les services backend Aug 14, 2025 pm 03:32 PM

GOTYPICAL OFFERSBETTERRUNTIMEPERFORMANCE AVEC LA MAINTRÉE DE PUTHROUGHTANDLOWERLATENCE, ENTERTFORI / O-HEAVYSERVICES, DUETOITSLIGHT LONDEGOROUTINESANDERFICENTSCHEDULL

Comment travailler avec JSON à Java Comment travailler avec JSON à Java Aug 14, 2025 pm 03:40 PM

ToworkwithJSONinJava,useathird-partylibrarylikeJackson,Gson,orJSON-B,asJavalacksbuilt-insupport;2.Fordeserialization,mapJSONtoJavaobjectsusingObjectMapperinJacksonorGson.fromJson;3.Forserialization,convertJavaobjectstoJSONstringsviawriteValueAsString

Quel est le mot-clé Assert en Java? Quel est le mot-clé Assert en Java? Aug 17, 2025 am 12:52 AM

TheassertKeywordInjavaisUsedTovalIdateShandshandingsDuringDevelopment, ThrowinganAssertionErroriftheconditionisfalse.2.ithastwoforms: AssertCondition; AndSersertCondition: Message; avecthelatterProvidActureCustomerMessage.3.

See all articles