0. code de question pionnier
Le code suivant montre un compteur où deux fils s'accumulent en même temps, chacun effectuant 1000 000 fois. Le résultat que nous attendons est définitivement i = 2000000. Cependant, après l'avoir exécuté plusieurs fois, nous constaterons que la valeur de I sera toujours inférieure à 2000000. En effet, lorsque deux threads écrivent I en même temps, le résultat de l'un des fils écrasera l'autre.
classe publique AccountingSync implémente Runnable {static int i = 0; public void augmentation () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {augmente (); }} public static void main (String [] args) lève InterruptedException {AccountingSync AccountingSync = new AccountingSync (); Thread t1 = nouveau thread (comptvalSync); Thread t2 = nouveau thread (comptvalSync); 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 doivent être entièrement synchronisés lors du fonctionnement i. C'est-à-dire que lorsque le fil A a écrit I, le fil B ne peut pas seulement écrire, mais ne peut pas non plus le lire.
1. Le rôle des mots clés synchronisés
La fonction du mot-clé synchronisé est en fait de réaliser la synchronisation entre les threads. Son travail consiste à verrouiller le code synchronisé, afin qu'un seul thread puisse entrer le bloc de synchronisation à la fois, assurant ainsi la sécurité entre les threads. Tout comme dans le code ci-dessus, l'opération I ++ ne peut être exécutée que par un autre thread en même temps.
2.Usage des mots clés synchronisés
Spécifiez le verrouillage de l'objet: verrouiller l'objet donné, entrez le bloc de code de synchronisation pour obtenir le verrouillage de l'objet donné
Agissant directement sur la méthode d'instance: il équivaut à verrouiller l'instance actuelle. En entrant dans le bloc de code synchrone, vous devez obtenir le verrouillage de l'instance actuelle (cela nécessite que lors de la création de thread, vous devez utiliser la même instance exécutable)
Agissant directement sur des méthodes statiques: il équivaut à verrouiller la classe actuelle. Avant d'entrer dans le bloc de code synchrone, vous devez obtenir le verrou de la classe actuelle.
2.1 Spécifiez l'objet pour verrouiller
Le code suivant s'applique synchronisé à un objet donné. Il y a une note ici que l'objet donné doit être statique, sinon nous ne partagerons pas l'objet entre nous chaque fois que nous avons nouveau un fil, donc la signification du verrouillage n'existera plus.
classe publique AccountingSync implémente Runnable {final static objet objet = new object (); statique int i = 0; public void augmentation () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {synchronisé (objet) {augmentation (); }}} public static void main (String [] args) lève InterruptedException {thread t1 = new Thread (new AccountingSync ()); Thread t2 = nouveau thread (new compcountingSync ()); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} 2.2 agir directement sur la méthode d'instance
Le mot-clé synchronisé agit sur la méthode d'instance, c'est-à-dire avant d'entrer la méthode augmentation (), le thread doit obtenir le verrouillage de l'instance actuelle. Cela nous oblige à utiliser la même instance d'objet exécutable lors de la création de l'instance de thread. Sinon, les serrures du fil ne sont pas sur la même instance, il n'y a donc aucun moyen de parler du problème de verrouillage / synchronisation.
classe publique AccountingSync implémente Runnable {static int i = 0; VOID Synchronisé public augmenté () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {augmente (); }} public static void main (String [] args) lève InterruptedException {AccountingSync AccountingSync = new AccountingSync (); Thread t1 = nouveau thread (comptvalSync); Thread t2 = nouveau thread (comptvalSync); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} Veuillez faire attention aux trois premières lignes de la méthode principale pour illustrer l'utilisation correcte des mots clés sur la méthode d'instance.
2.3 Agissant directement sur des méthodes statiques
Pour appliquer le mot clé synchronisé à la méthode statique, il n'est pas nécessaire d'utiliser les deux threads pour pointer la même méthode exécutable que dans l'exemple ci-dessus. Étant donné que le bloc de méthode doit demander le verrouillage de la classe actuelle, pas l'instance actuelle, les threads peuvent toujours être synchronisés correctement.
classe publique AccountingSync implémente Runnable {static int i = 0; VOID Synchronisé statique public augmenté () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {augmente (); }} public static void main (String [] args) lève InterruptedException {thread t1 = new Thread (new AccountingSync ()); Thread t2 = nouveau thread (new compcountingSync ()); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }}3. Verrouillage incorrect
À partir de l'exemple ci-dessus, nous savons que si nous avons besoin d'une application de compteur, afin d'assurer l'exactitude des données, nous aurons naturellement besoin de verrouiller le compteur, afin que nous puissions écrire le code suivant:
classe publique BadLockonInteger implémente Runnable {static entier i = 0; @Override public void run () {for (int j = 0; j <1000000; j ++) {synchronisé (i) {i ++; }}} public static void main (String [] args) lève InterruptedException {badlockonInteger badlockonInteger = new BadLockonInteger (); Thread t1 = nouveau thread (badlockonInteger); Thread t2 = nouveau thread (badlockonInteger); 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 signifie que le fil n'est pas sûr.
Pour expliquer ce problème, nous devons commencer par Integer: en Java, Integer est un objet invariant. Comme String, une fois un objet créé, il ne peut pas être modifié. Si vous avez un entier = 1, alors ce sera toujours 1. Et si vous voulez cet objet = 2? Vous ne pouvez recréer qu'un entier. Après chaque I ++, il équivaut à appeler la valeur de la méthode de l'entier. Jetons un coup d'œil au code source de la valeur de la valeur de la valeur d'Integer:
Public Static Integer ValueOf (int i) {if (i> = IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache [i + (-IntegerCache.low)]; retourner un nouvel entier (i);} Integer.ValueOf () est en fait une méthode d'usine, qui a tendance à renvoyer un nouvel objet entier et à copier la valeur à i;
Par conséquent, nous connaissons la raison du problème. Étant donné entre plusieurs threads, puisque I ++ arrive après moi, je pointe vers un nouvel objet, le thread peut charger des instances d'objet différentes chaque fois qu'elle est verrouillée. La solution est très simple. Vous pouvez le résoudre en utilisant l'une des trois méthodes de synchronisation ci-dessus.