1. Utilisation de base de synchronisé
Synchronisé est la méthode la plus couramment utilisée pour résoudre les problèmes de concurrence en Java et la méthode la plus facile. Synchronisé a trois fonctions principales: (1) Assurer le thread Code de synchronisation d'accès mutuellement exclusif (2) Assurez-vous que la modification des variables partagées peut être visible en temps opportun (3) résoudre efficacement le problème de réorganisation. Synchronisé a trois utilisations de synchronisés:
(1) méthode ordinaire de modification
(2) modifier les méthodes statiques
(3) Modifier le bloc de code
Ensuite, j'utiliserai quelques exemples de programmes pour illustrer ces trois méthodes d'utilisation (par souci de comparaison, à l'exception des différentes méthodes d'utilisation de la synchronisation, les trois autres codes sont fondamentalement cohérents).
1. Pas de synchronisation:
Extrait de code 1:
package com.paddx.test.concurrent; public class synchronizedTest {public void method1 () {System.out.println ("Method 1 start"); try {System.out.println ("Method 1 Execute"); Thread.Sleep (3000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 1 end"); } public void method2 () {System.out.println ("méthode 2 start"); try {System.out.println ("Method 2 Execute"); Thread.Sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 2 end"); } public static void main (String [] args) {final synchronisé test = new SynchronizedTest (); nouveau thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); nouveau thread (new Runnable () {@Override public void run () {test.method2 ();}}). start (); }}Le résultat de l'exécution est le suivant: le thread 1 et le thread 2 entrent en même temps l'état d'exécution. Le thread 2 exécute plus rapidement que le thread 1, donc le thread 2 s'exécute d'abord. Dans ce processus, le thread 1 et le thread 2 s'exécutent en même temps.
Méthode 1 Démarrer
Méthode 1 Exécuter
Méthode 2 Démarrer
Méthode 2 Exécuter
Méthode 2 Fin
Méthode 1 fin
2. Synchroniser les méthodes communes:
Code Snippet deux:
package com.paddx.test.concurrent; public class synchronizedTest {public synchronisé void method1 () {System.out.println ("méthode 1 start"); try {System.out.println ("Method 1 Execute"); Thread.Sleep (3000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 1 end"); } public synchronisé void méthode2 () {System.out.println ("Method 2 start"); try {System.out.println ("Method 2 Execute"); Thread.Sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 2 end"); } public static void main (String [] args) {final synchronisé test = new SynchronizedTest (); nouveau thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); nouveau thread (new Runnable () {@Override public void run () {test.method2 ();}}). start (); }}Le résultat de l'exécution est le suivant. Après l'avoir comparé avec le segment de code, on peut voir clairement que le thread 2 doit attendre l'exécution de la méthode1 du thread 1 à terminer avant de commencer à exécuter la méthode Method2.
Méthode 1 Démarrer
Méthode 1 Exécuter
Méthode 1 fin
Méthode 2 Démarrer
Méthode 2 Exécuter
Méthode 2 Fin
3. Méthode statique (classe) Synchronisation
Code Snippet Trois:
package com.paddx.test.concurrent; classe publique SynchronizedTest {public static synchronisé void méthode1 () {System.out.println ("Method 1 start"); try {System.out.println ("Method 1 Execute"); Thread.Sleep (3000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 1 end"); } public static synchronisé void méthode2 () {System.out.println ("Method 2 start"); try {System.out.println ("Method 2 Execute"); Thread.Sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 2 end"); } public static void main (String [] args) {final synchronisé test = new SynchronizedTest (); Final SynchronizedTest test2 = new SynchronizedTest (); nouveau thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); nouveau thread (new Runnable () {@Override public void run () {test2.method2 ();}}). start (); }}Le résultat de l'exécution est le suivant. La synchronisation des méthodes statiques est essentiellement une synchronisation des classes (les méthodes statiques sont essentiellement des méthodes de classe, et non des méthodes sur les objets). Par conséquent, même si le test et le test2 appartiennent à différents objets, ils appartiennent tous les deux à des instances de la classe SynchronizedTest, de sorte que la méthode1 et la méthode2 ne peuvent être exécutées que séquentiellement et ne peuvent pas être exécutées simultanément.
Méthode 1 Démarrer
Méthode 1 Exécuter
Méthode 1 fin
Méthode 2 Démarrer
Méthode 2 Exécuter
Méthode 2 Fin
4. Synchronisation du bloc de code
Code Snippet Four:
package com.paddx.test.concurrent; public class synchronizedTest {public void method1 () {System.out.println ("Method 1 start"); try {synchronisé (this) {System.out.println ("Method 1 Execute"); Thread.Sleep (3000); }} catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 1 end"); } public void method2 () {System.out.println ("méthode 2 start"); try {synchronisé (this) {System.out.println ("Method 2 Execute"); Thread.Sleep (1000); }} catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Method 2 end"); } public static void main (String [] args) {final synchronisé test = new SynchronizedTest (); nouveau thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); nouveau thread (new Runnable () {@Override public void run () {test.method2 ();}}). start (); }}Le résultat de l'exécution est le suivant. Bien que le thread 1 et le thread 2 entrent la méthode correspondante et le démarrage de l'exécution, le thread 2 doit attendre l'exécution du bloc de synchronisation dans le thread 1 pour se terminer avant d'entrer le bloc de synchronisation.
Méthode 1 Démarrer
Méthode 1 Exécuter
Méthode 2 Démarrer
Méthode 1 fin
Méthode 2 Exécuter
Méthode 2 Fin
2. Principe synchronisé
Si vous avez encore des questions sur les résultats d'exécution ci-dessus, ne vous inquiétez pas. Comprenons d'abord le principe de synchronisé, puis regardons les questions ci-dessus à voir en un coup d'œil. Voyons d'abord comment le code synchronisé synchronise les blocs de code en décompilant le code suivant:
package com.paddx.test.concurrent; public class synchronizeddemo {public void method () {synchronisé (this) {System.out.println ("Method 1 start"); }}}Résultat de la décompilation:
En ce qui concerne le rôle de ces deux instructions, nous nous référons directement à la description dans la spécification JVM:
Moniteur de surveillance:
Chaque objet est associé à un moniteur. Un moniteur est verrouillé si et seulement s'il a un propriétaire. Le thread qui exécute le surveillant tente de gagner la propriété du moniteur associé à Objectref, comme suit: • Si le nombre d'entrée du moniteur associé à Objectref est zéro, le thread entre le moniteur et définit son nombre d'entrée à un. Le thread est alors le propriétaire du moniteur.
La signification générale de ce passage est:
Chaque objet a un verrouillage de moniteur (moniteur). Lorsque le moniteur est occupé, il sera verrouillé. Lorsque le thread exécute l'instruction du surveillant, il essaie d'obtenir la propriété du moniteur. Le processus est le suivant:
1. Si le nombre d'entrée du moniteur est 0, le thread entre dans le moniteur, puis définit le numéro d'entrée sur 1, le thread est le propriétaire du moniteur.
2. Si le thread possède déjà le moniteur et rentre simplement, le nombre d'entrée dans le moniteur est ajouté à 1.
3. Si d'autres threads ont occupé le moniteur, le thread entre dans un état de blocage jusqu'à ce que le nombre d'entrée du moniteur soit 0, puis essayez d'obtenir à nouveau la propriété du moniteur.
monitorexit:
Le thread qui exécute monitorexit doit être le propriétaire du moniteur associé à l'instance référencée par Objectref.Le thread diminue le nombre d'entrée du moniteur associé à Objectref. Si, par conséquent, la valeur du nombre d'entrée est nulle, le thread quitte le moniteur et n'est plus son propriétaire. D'autres fils qui bloquent pour entrer le moniteur sont autorisés à tenter de le faire.
La signification générale de ce passage est:
Le thread exécutant monitorexit doit être le propriétaire du moniteur correspondant à Objectref.
Lorsque l'instruction est exécutée, le nombre de moniteurs entrant est réduit de 1. Si le nombre de moniteurs entrant est 0 après décrémentation de 1, le thread quitte le moniteur et n'est plus le propriétaire de ce moniteur. D'autres fils bloqués par ce moniteur peuvent essayer d'obtenir la propriété de ce moniteur.
Grâce à ces deux paragraphes de description, nous devrions être en mesure de voir clairement le principe de mise en œuvre de la synchronisation. La couche sémantique sous-jacente de synchronisée est terminée via un objet de moniteur. En fait, l'attente / notifier et d'autres méthodes reposent également sur les objets de moniteur. C'est pourquoi seules des méthodes telles que Wait / Notify peuvent être appelées dans des blocs ou des méthodes synchronisés, sinon une exception de Java.lang.LelegalMonitorStateException sera lancée.
Examinons les résultats de décompilation de la méthode de synchronisation:
Code source:
package com.paddx.test.concurrent; classe publique SynchronizedMethod {public synchronisé void méthode () {System.out.println ("Hello World!"); }}Résultat de la décompilation:
À en juger par les résultats de la décompilation, la synchronisation de la méthode n'est pas terminée via le surveillant des instructions et le monitorexit (en théorie, il peut également être mis en œuvre par le biais de ces deux instructions). Cependant, par rapport aux méthodes ordinaires, l'identifiant ACC_Synchronisé est ajouté à son pool constant. JVM implémente la synchronisation des méthodes basées sur cet identifiant: lorsque la méthode est appelée, l'instruction d'appel vérifiera si l'indicateur d'accès ACC_SYNCHRONISED de la méthode est défini. S'il est défini, le thread d'exécution obtiendra d'abord le moniteur, puis exécutera le corps de la méthode une fois la méthode exécutée avec succès. Une fois la méthode exécutée, le moniteur sera libéré. Pendant l'exécution de la méthode, aucun autre thread ne peut plus obtenir le même objet de moniteur. En fait, il n'y a pas de différence d'essence, mais la synchronisation de la méthode est un moyen implicite de l'atteindre sans avoir besoin d'être fait via Bytecode.
3. Explication des résultats de l'opération
Avec une compréhension du principe de synchronisé, vous pouvez facilement le résoudre en regardant le programme ci-dessus.
1. Résultats du segment de code 2:
Bien que la méthode1 et la méthode2 soient différentes méthodes, les deux méthodes sont synchronisées et sont appelées via le même objet. Par conséquent, avant d'appeler, vous devez rivaliser pour le verrou (moniteur) sur le même objet, vous ne pouvez donc obtenir que les verrous mutuellement exclusivement. Par conséquent, la méthode1 et la méthode2 ne peuvent être exécutées que séquentiellement.
2. Résultats du segment de code 3:
Bien que le test et le test2 appartiennent à différents objets, le test et le test2 appartiennent à différentes instances de la même classe. Étant donné que Method1 et Method2 appartiennent tous deux à des méthodes de synchronisation statiques, vous devez obtenir le moniteur sur la même classe (chaque classe ne correspond qu'à un objet de classe), vous ne pouvez donc exécuter séquentiellement.
3. Résultats du segment de code 4:
Pour la synchronisation des blocs de code, il est essentiellement nécessaire d'obtenir le moniteur de l'objet entre crochets après le mot clé synchronisé. Étant donné que le contenu des supports dans ce code est celui-ci, et que Method1 et Method2 sont appelés via le même objet, donc avant d'entrer dans le bloc de synchronisation, vous devez rivaliser pour les verrous sur le même objet, de sorte que le bloc de synchronisation ne peut être exécuté qu'en séquence.
Quatre résumé
Synchronisé est la méthode la plus couramment utilisée pour la sécurité des threads dans la programmation simultanée Java, et il est relativement simple à utiliser. Cependant, si nous pouvons comprendre ses principes en profondeur et avoir une certaine compréhension des connaissances sous-jacentes telles que les verrous du moniteur, il peut nous aider à utiliser correctement les mots clés synchronisés, et en revanche, il peut également nous aider à mieux comprendre le mécanisme de programmation de concurrence, nous aider à choisir de meilleures stratégies de concurrence pour accomplir des tâches dans différentes circonstances. Vous pouvez également faire face calmement à divers problèmes simultanés que vous rencontrez dans la vie quotidienne.