Dans le traitement simultané des threads Java, il existe actuellement beaucoup de confusion quant à l'utilisation du mot-clé volatile. On pense qu'en utilisant ce mot-clé, tout ira bien lors de l'exécution d'un traitement simultané multi-thread.
Le langage Java prend en charge le multithreading. Afin de résoudre le problème de la concurrence des threads, les mécanismes de bloc synchronisé et de mot clé volatile sont introduits dans le langage.
synchronisé
Tout le monde connaît les blocs synchronisés, qui sont implémentés via le mot-clé synchronisé. Avec l'ajout d'instructions synchronisées et de bloc, lorsqu'il est accessible par plusieurs threads, un seul thread peut utiliser des méthodes modifiées synchronisées ou des blocs de code en même temps.
volatil
Pour les variables modifiées avec volatile, chaque fois que le thread utilise la variable, il lira la valeur modifiée de la variable. Le volatile peut facilement être utilisé à mauvais escient pour effectuer des opérations atomiques.
Regardons un exemple ci-dessous. Nous implémentons un compteur. Chaque fois que le thread démarre, la méthode counter inc sera appelée pour en ajouter un au compteur.
Environnement d'exécution - version jdk : jdk1.6.0_31, mémoire : 3G cpu : x86 2.4G
Copiez le code comme suit :
compteur de classe publique {
nombre d'ints statiques publics = 0 ;
public statique vide inc() {
//Retardez 1 milliseconde ici pour rendre le résultat évident
essayer {
Thread.sleep(1);
} catch (InterruptedException e) {
}
compte++;
}
public static void main (String[] arguments) {
//Démarrez 1000 threads en même temps pour effectuer des calculs i++ et voir les résultats réels
pour (int je = 0; je < 1000; i++) {
nouveau fil (nouveau Runnable() {
@Outrepasser
public void run() {
Counter.inc();
}
}).commencer();
}
//La valeur ici peut être différente à chaque exécution, éventuellement 1 000
System.out.println("Exécuter le résultat : Counter.count=" + Counter.count);
}
}
Résultat d'exécution : Counter.count=995
Le résultat réel de l'opération peut être différent à chaque fois. Le résultat de cette machine est : résultat en cours d'exécution : Counter.count=995. On peut voir que dans un environnement multithread, Counter.count ne s'attend pas à ce que le résultat soit 1000.
Beaucoup de gens pensent qu'il s'agit d'un problème de concurrence multithread. Il suffit d'ajouter volatile avant le nombre de variables pour éviter ce problème. Ensuite, nous modifierons le code pour voir si le résultat répond à nos attentes.
Copiez le code comme suit :
compteur de classe publique {
nombre d'ints statiques volatiles publics = 0 ;
public statique vide inc() {
//Retardez 1 milliseconde ici pour rendre le résultat évident
essayer {
Thread.sleep(1);
} catch (InterruptedException e) {
}
compte++;
}
public static void main (String[] arguments) {
//Démarrez 1000 threads en même temps pour effectuer des calculs i++ et voir les résultats réels
pour (int je = 0; je < 1000; i++) {
nouveau fil (nouveau Runnable() {
@Outrepasser
public void run() {
Counter.inc();
}
}).commencer();
}
//La valeur ici peut être différente à chaque exécution, éventuellement 1 000
System.out.println("Exécuter le résultat : Counter.count=" + Counter.count);
}
}
Résultat d'exécution : Counter.count=992
Le résultat courant n’est toujours pas 1000 comme prévu. Analysons les raisons ci-dessous.
Dans l'article Java Garbage Collection, l'allocation de mémoire pendant l'exécution de la JVM est décrite. L'une des zones de mémoire est la pile de machine virtuelle JVM. Chaque thread possède une pile de threads lorsqu'il est en cours d'exécution. La pile de threads stocke les informations sur les valeurs variables lorsque le thread est en cours d'exécution. Lorsqu'un thread accède à la valeur d'un objet, il trouve d'abord la valeur de la variable correspondant à la mémoire du tas via la référence d'objet, puis charge la valeur spécifique de la variable de mémoire du tas dans la mémoire locale du thread pour créer une copie du variable.Après cela, le thread n'a rien à voir avec la valeur de la variable de l'objet dans la mémoire du tas, mais modifie directement la valeur de la variable de copie. À un certain moment après la modification (avant la sortie du thread), la valeur de la copie de la variable de thread est automatiquement réécrite dans la variable de l'objet dans le tas. De cette façon, la valeur de l’objet dans le tas change. L'image ci-dessous
Décrivez cette interaction
lire et charger des copies de variables de la mémoire principale vers la mémoire de travail actuelle
utiliser et attribuer du code d'exécution et modifier les valeurs des variables partagées
stocker et écrire actualiser le contenu lié à la mémoire principale avec les données de la mémoire de travail
où l'utilisation et l'attribution peuvent apparaître plusieurs fois
Cependant, ces opérations ne sont pas atomiques. Autrement dit, après le chargement en lecture, si la variable de comptage de la mémoire principale est modifiée, la valeur dans la mémoire de travail du thread ne changera pas car elle a été chargée, donc le résultat calculé sera comme prévu. le même
Pour les variables volatiles modifiées, la machine virtuelle JVM garantit uniquement que la valeur chargée de la mémoire principale vers la mémoire de travail du thread est la dernière
Par exemple, si les threads 1 et 2 constatent que la valeur de comptage dans la mémoire principale est à la fois 5 lors des opérations de lecture et de chargement, ils chargeront la dernière valeur.
Une fois le nombre de tas du thread 1 modifié, il sera écrit dans la mémoire principale et la variable count dans la mémoire principale deviendra 6.
Étant donné que le thread 2 a déjà effectué des opérations de lecture et de chargement, après avoir effectué l'opération, il mettra également à jour la valeur de la variable du nombre de mémoire principale à 6.
Par conséquent, une fois que deux threads ont été modifiés dans le temps à l’aide du mot-clé volatile, il y aura toujours une concurrence.