1. Quand les problèmes de sécurité des filetages se produisent-ils?
Il n'y aura pas de problèmes de sécurité de fil dans un seul thread, mais dans la programmation multi-thread, il est possible d'accéder à la même ressource en même temps. Cette ressource peut être différents types de ressources: une variable, un objet, un fichier, une table de base de données, etc. Lorsque plusieurs threads accèdent à la même ressource en même temps, il y aura un problème:
Étant donné que le processus exécuté par chaque thread est incontrôlable, il est probable que le résultat final sera contraire au souhait réel ou conduira directement à des erreurs de programme.
Donnons un exemple simple:
Maintenant, il y a deux threads qui lisent les données du réseau séparément, puis les insérent dans une table de base de données, nécessitant que les données en double ne peuvent pas être insérées.
Ensuite, il doit y avoir deux opérations dans le processus d'insertion des données:
1) Vérifiez si les données existent dans la base de données;
2) S'il existe, il ne sera pas inséré; S'il n'existe pas, il sera inséré dans la base de données.
Si les deux threads sont représentés respectivement par Thread-1 et Thread-2, et à un moment donné, Thread-1 et Thread-2 lisent les deux données X, cela peut se produire:
Thread-1 vérifie si les données X existent dans la base de données et Thread-2 vérifie également si les données X existent dans la base de données.
En conséquence, le résultat de la vérification des deux threads est que les données x n'existent pas dans la base de données, donc les deux threads insérent respectivement les données X dans la table de la base de données.
Il s'agit d'un problème de sécurité de thread, c'est-à-dire que lorsque plusieurs threads accèdent à une ressource en même temps, le résultat d'exécution du programme n'est pas le résultat que vous souhaitez voir.
Ici, cette ressource est appelée: ressource critique (également connue sous le nom de ressource partagée).
C'est-à-dire que lorsque plusieurs threads accèdent aux ressources critiques (un objet, aux attributs dans un objet, un fichier, une base de données, etc.), des problèmes de sécurité des threads peuvent survenir.
Cependant, lorsque plusieurs threads exécutent une méthode, les variables locales à l'intérieur de la méthode ne sont pas des ressources critiques, car la méthode est exécutée sur la pile, tandis que la pile Java est privée, il n'y aura donc pas de problèmes de sécurité de thread.
2. Comment résoudre les problèmes de sécurité des filetages?
Donc, en général, comment résoudre les problèmes de sécurité des filetages?
Fondamentalement, lors de la résolution des problèmes de sécurité des fils, tous les modes de concurrence adoptent la solution "Accès sérialisé aux ressources critiques", c'est-à-dire, en même temps, un seul thread peut accéder aux ressources critiques, également appelées accès mutuellement exclusifs synchrones.
D'une manière générale, un verrou est ajouté avant le code qui accède à la ressource critique. Après avoir accédé à la ressource critique, le verrou est libéré et d'autres threads continuent d'accéder.
En Java, deux manières sont fournies pour implémenter l'accès synchrone mutex: synchronisé et verrouillage.
Cet article parle principalement de l'utilisation de la synchronisation et l'utilisation de Lock est décrite dans le prochain article de blog.
Trois. Méthode de synchronisation Synchronisée ou bloc de synchronisation
Avant de comprendre l'utilisation de mots clés synchronisés, examinons d'abord un concept: Mutex Locks, comme son nom l'indique: Locks qui peuvent atteindre l'objectif de l'accès Mutex.
Pour donner un exemple simple: si une ressource critique est ajoutée à un mutex, lorsqu'un thread accède à la ressource critique, les autres threads ne peuvent qu'attendre.
En Java, chaque objet a une marque de verrouillage (moniteur), également connu sous le nom de moniteur. Lorsque plusieurs threads accèdent à un objet en même temps, le thread ne peut y accéder que s'il acquiert le verrou de l'objet.
Dans Java, le mot-clé synchronisé peut être utilisé pour marquer une méthode ou un bloc de code. Lorsqu'un thread appelle la méthode synchronisée de l'objet ou accède au bloc de code synchronisé, le thread obtient le verrou de l'objet. D'autres threads ne peuvent pas accéder à la méthode pour le moment. Ce n'est que lorsque la méthode est exécutée ou que le bloc de code est exécuté que le thread libérera le verrou de l'objet, et d'autres threads peuvent exécuter la méthode ou le bloc de code.
Voici quelques exemples simples pour illustrer l'utilisation de mots clés synchronisés:
1. Méthode synchronisée
Dans le code suivant, deux threads appellent l'objet InsertData pour insérer des données:
Public Class Test {public static void main (String [] args) {final insertData insertData = new insertData (); nouveau thread () {public void run () {insertData.insert (thread.currentThread ()); }; }.commencer(); nouveau thread () {public void run () {insertData.insert (thread.currentThread ()); }; }.commencer(); }} classe insertdata {private arrayList <Integer> arrayList = new ArrayList <Integer> (); insert public public (thread thread) {for (int i = 0; i <5; i ++) {System.out.println (thread.getName () + "Insérer des données" + i); arrayList.add (i); }}} À l'heure actuelle, le résultat de sortie du programme est:
Cela montre que deux threads exécutent la méthode d'insertion en même temps.
Si le mot-clé synchronisé est ajouté avant la méthode d'insertion, le résultat d'exécution est:
classe insertdata {private arrayList <Integer> arrayList = new ArrayList <Integer> (); insert de void synchronisé public (thread thread) {for (int i = 0; i <5; i ++) {System.out.println (thread.getName () + "Insérer des données" + i); arrayList.add (i); }}}À partir de la sortie ci-dessus, les données insérées dans Thread-1 ne sont effectuées qu'après l'insertion de Thread-0. Cela montre que Thread-0 et Thread-1 exécutent séquentiellement les méthodes d'insertion.
Ceci est la méthode synchronisée.
Cependant, il y a quelques points à noter:
1) Lorsqu'un thread accède à la méthode synchronisée d'un objet, d'autres threads ne peuvent pas accéder aux autres méthodes synchronisées de l'objet. Cette raison est très simple, car un objet n'a qu'une seule serrure. Lorsqu'un thread acquiert le verrou de l'objet, d'autres threads ne peuvent pas obtenir le verrou de l'objet, ils ne peuvent donc pas accéder à d'autres méthodes synchronisées de l'objet.
2) Lorsqu'un thread accède à la méthode synchronisée d'un objet, d'autres threads peuvent accéder à la méthode non synchronisée de l'objet. La raison en est très simple. L'accès aux méthodes non synchronisées ne nécessite pas le verrou de l'objet. Si une méthode n'est pas modifiée avec le mot clé synchronisé, cela signifie qu'il n'utilisera pas de ressources critiques, alors d'autres threads peuvent accéder à cette méthode.
3) Si un thread A a besoin pour accéder à la méthode synchronisée Fun1 de Object Object1, et qu'un autre thread B doit accéder à la méthode synchronisée Fun1 de Object Object2, même si Object1 et Object2 sont du même type), il n'y aura pas de problème de sécurité de thread, car il accéde à différents objets, il n'y a donc pas de problème d'exclusion mutuel.
2. Bloc de code synchronisé
Les blocs de code synchronisés sont similaires au formulaire suivant:
synchronisé (syncobject) {
}
Lorsque ce bloc de code est exécuté dans un thread, le thread acquiert le verrou du Synobject d'objet, ce qui rend impossible pour d'autres threads d'accéder au bloc de code en même temps.
Le Synobject peut être le suivant, représentant le verrou qui acquiert l'objet actuel, ou il peut être un attribut dans la classe, représentant le verrou qui acquiert l'attribut.
Par exemple, la méthode d'insertion ci-dessus peut être modifiée par les deux formulaires suivants:
classe insertdata {private arrayList <Integer> arrayList = new ArrayList <Integer> (); public void insert (thread thread) {synchronisé (this) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName () + "insert data" + i); arrayList.add (i); }}}} classe insertdata {private arrayList <Integer> arrayList = new ArrayList <Integer> (); objet privé objet = nouveau objet (); public void insert (thread thread) {synchronisé (objet) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName () + "insérer les données" + i); arrayList.add (i); }}}}Comme on peut le voir à partir de ce qui précède, les blocs de code synchronisés sont beaucoup plus flexibles à utiliser que les méthodes synchronisées. Parce que peut-être qu'une seule partie du code dans une méthode doit être synchronisée, si la méthode entière est synchronisée pour le moment, cela affectera l'efficacité d'exécution du programme. Ce problème peut être évité en utilisant des blocs de code synchronisés. Les blocs de code synchronisés ne peuvent se synchroniser que lorsque la synchronisation est requise.
De plus, chaque classe a également un verrou, qui peut être utilisé pour contrôler l'accès simultané aux membres de données statiques.
Et si un thread exécute la méthode synchronisée non statique d'un objet, et qu'un autre thread doit exécuter la méthode synchronisée statique de la classe à laquelle appartient l'objet, il n'y aura pas d'exclusion mutuelle à l'heure actuelle, car l'accès à la méthode synchronisée statique occupe un verrouillage de classe, tout en accédant à la méthode synchronisée non statique occupe un verrouillage d'objet, donc il n'y a aucune exclusion mutuelle.
Vous comprendrez en examinant le code suivant:
Public Class Test {public static void main (String [] args) {final insertData insertData = new insertData (); nouveau thread () {@Override public void run () {inserdata.insert (); } }.commencer(); nouveau thread () {@Override public void run () {insertData.insert1 (); } }.commencer(); }} class insertData {public synchronisé void insert () {System.out.println ("exécuter l'insert"); essayez {thread.sleep (5000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println ("Execute insert1"); } public synchronisé static void insert1 () {System.out.println ("exécuter insert1"); System.out.println ("EXECUTE INSERT1"); }} Résultats de l'exécution;
La méthode d'insert est exécutée dans le premier thread, ce qui ne fera pas bloquer le deuxième thread à bloquer la méthode INSERT1.
Jetons un coup d'œil à ce que fait le mot-clé synchronisé. Décompilons ses bytecodes. Le bytecode décompilé du code suivant est:
classe publique insertData {objet privé objet = nouveau objet (); insert public public (thread thread) {synchronisé (objet) {}} public synchronisé void insert1 (thread thread) {} public void insert2 (thread thread) {}}À partir du bytecode obtenu par décompilation, on peut voir que le bloc de code synchronisé a en fait deux instructions: le surveillant et le monitorexit. Lorsque l'instruction du surveillant est exécutée, le nombre de verrouillage de l'objet sera augmenté de 1, tandis que lorsque l'instruction de monitorexit sera exécutée, le nombre de verrouillage de l'objet sera réduit de 1. En fait, cela est très similaire à l'opération PV dans le système d'exploitation. L'opération PV dans le système d'exploitation est utilisée pour contrôler plusieurs threads pour accéder aux ressources critiques. Pour la méthode synchronisée, le thread dans l'exécution reconnaît si la structure Method_info de la méthode a le paramètre de signalement Acc_Synchronisé, puis il acquiert automatiquement le verrou de l'objet, appelle la méthode et libère enfin le verrou. Si une exception se produit, le thread libère automatiquement le verrou.
Une chose à noter: pour les méthodes synchronisées ou les blocs de code synchronisés, lorsqu'une exception se produit, le JVM libérera automatiquement la serrure occupée par le thread actuel, il n'y aura donc pas de blocage en raison de l'exception.
3. Quelques autres choses notables à propos de la synchronisation
1. La différence entre synchronisé et statique synchronisé
Synchronisé verrouille l'instance actuelle de la classe pour empêcher d'autres threads d'accéder à tous les blocs synchronisés de l'instance de la classe en même temps. Notez qu'il s'agit de «l'instance actuelle de la classe», et il n'y a pas de contrainte de ce type sur deux instances différentes de la classe. Ensuite, STATIC synchronisé arrive pour contrôler l'accès à toutes les instances de la classe. STATIC Synchronisé restreint les threads pour accéder à toutes les instances de la classe dans JVM en même temps et accéder rapidement au code correspondant. En fait, s'il est synchronisé dans une méthode ou un bloc de code dans une classe, puis après avoir généré une instance de cette classe, la classe aura une surveillance rapide et l'accès simultané au thread à l'instance modifiée synchronisée est protégé rapidement. Synchronisé statique est le public de la surveillance, qui est la différence entre les deux. Autrement dit, synchronisé est équivalent à cela.
Un auteur japonais, "Java Multithread Design Pattern" de Jie Chenghao a une chronique comme celle-ci:
Classe pulbic quelque chose () {public synchronisé void issynca () {} public synchronisé void issyncb () {} public statique synchronisé void cSynca () {} public statique synchronisé void cSyncb () {}} Ensuite, si deux instances A et B de quelque chose de classe sont ajoutées, pourquoi le groupe de méthodes suivant peut-il être accessible simultanément par plus d'un thread? axissynca () et x.issyncb ()
bxissynca () et y.issynca ()
cxcSynca () et y.csyncb ()
dxissynca () et quelque chose.csynca ()
Ici, il est clair qu'il peut être jugé:
A, à la fois l'accès au domaine synchronisé de la même instance, il ne peut donc pas être accessible en même temps. B est pour différentes instances, donc il est accessible en même temps. Parce qu'il est synchronisé statique, différentes instances seront toujours restreintes, ce qui équivaut à quelque chose.issynca () et quelque chose.issyncb (), il ne peut donc pas être accessible en même temps.
Alors, qu'en est-il de D ?, La réponse dans le livre est accessible simultanément. La raison de la réponse est que synchronzed est que la méthode d'instance et la méthode de classe synchronzée sont différentes du verrou.
L'analyse personnelle signifie que synchronisé et statique synchronisé équivaut à deux gangs, chacun ayant son propre contrôle, et il n'y a aucune contrainte les uns sur les autres et est accessible en même temps. Il n'est pas clair comment la synchronisation est mise en œuvre dans la conception interne de Java.
Conclusion: A: Synchronisé statique est la portée d'une certaine classe. CSYNC statique synchronisé {} empêche plusieurs threads d'accéder à la méthode statique synchronisée de cette classe en même temps. Il fonctionne sur toutes les instances d'objet d'une classe.
B: Synchronisé est la portée d'une instance. synchronisé issync () {} empêche plusieurs threads d'accéder à la méthode synchronisée dans cette instance en même temps.
2. La différence entre la méthode synchronisée et le code synchronisé rapidement
Il n'y a pas de différence entre les méthodes synchronisées () {} et synchronisée (this) {}, mais les méthodes synchronisées () {} sont commode pour la compréhension de la lecture, tandis que synchronisée (this) {} peut contrôler plus précisément les conflits restreignent les zones d'accès, et parfois effectuer plus efficacement.
3. Le mot-clé synchronisé ne peut pas être hérité