Cet article suit principalement deux articles précédents de multi-threads pour résumer les problèmes de sécurité des fils dans le multi-threading Java.
1. Un exemple de sécurité de thread Java typique
classe publique threadtest {public static void main (String [] args) {compte compte = nouveau compte ("123456", 1000); Drawmoneyrunnable drawmoneyrunnable = new drawmoneyrunnable (compte, 700); Thread myThread1 = nouveau thread (drawmoneyrunnable); Thread myThread2 = nouveau thread (drawmoneyrunnable); mythread1.start (); mythread2.start (); }} classe DrawMoneyRunnable implémente Runnable {compte de compte privé; Double-Drawamount privé; public drawmoneyrunnable (compte de compte, double drawamount) {super (); this.account = compte; this.Drawamount = drawamount; } public void run () {if (compte.getBalance ()> = drawamount) {// 1 System.out.println ("Le retrait a été réussi, le retrait de l'argent est:" + drawamount); Double Balance = compte.getBalance () - DrawAmount; account.setBalance (solde); System.out.println ("L'équilibre est:" + Balance); }}} Class Compte {private String AccountNo; double équilibre privé; Public Account () {} public Account (String AccountNo, double bilan) {this.accountno = accountno; this.balance = équilibre; } public String getAccountno () {return accountNo; } public void setAccountNo (String Accountno) {this.accountno = accountno; } public double getBalance () {return Balance; } public void setBalance (double équilibre) {this.balance = équilibre; }}L'exemple ci-dessus est facile à comprendre. Il y a une carte bancaire avec un solde de 1 000. Le programme simule la scène où vous et votre femme retirez de l'argent au guichet automatique en même temps. Exécutez ce programme plusieurs fois et peut avoir des résultats de sortie dans plusieurs combinaisons différentes. L'une des sorties possibles est:
1 Le retrait de l'argent est réussi, le retrait de l'argent est: 700.0
2 L'équilibre est: 300.0
3 Le retrait de l'argent est réussi, le retrait de l'argent est: 700.0
4 Le solde est: -400.0
En d'autres termes, pour une carte bancaire avec un solde de seulement 1 000, vous pouvez retirer un total de 1 400, ce qui est évidemment un problème.
Après analyse, le problème réside dans l'incertitude de l'exécution dans un environnement multithread Java. Le CPU peut basculer au hasard entre plusieurs threads à l'état prêt, il est donc très probable que la situation suivante se produit: lorsque Thread1 exécute le code à // 1, la condition de jugement est vraie. À l'heure actuelle, le CPU passe à Thread2, exécute le code à // 1 et constate qu'il est toujours vrai. Ensuite, Thread2 est exécuté, puis passez à Thread1, puis l'exécution est terminée. À l'heure actuelle, les résultats ci-dessus apparaîtront.
Par conséquent, en ce qui concerne les problèmes de sécurité du fil, cela signifie en fait que l'accès à des ressources partagées dans un environnement multithread peut entraîner une incohérence dans cette ressource partagée. Par conséquent, pour éviter les problèmes de sécurité des threads, l'accès simultané à cette ressource partagée dans un environnement multithread doit être évité.
2. Méthode de synchronisation
La modification des mots clés synchronisée est ajoutée à la définition de la méthode pour accéder aux ressources partagées, ce qui rend cette méthode appelée méthode de synchronisation. On peut simplement comprendre que cette méthode est verrouillée et son objet verrouillé est l'objet lui-même où se trouve la méthode actuelle. Dans un environnement multi-thread, lors de l'exécution de cette méthode, vous devez d'abord obtenir ce verrouillage de synchronisation (et au plus un seul thread peut l'obtenir). Ce n'est que lorsque le thread exécute cette méthode de synchronisation que l'objet de verrouillage sera libéré, et d'autres threads peuvent obtenir ce verrouillage de synchronisation, et ainsi de suite ...
Dans l'exemple ci-dessus, la ressource partagée est un objet de compte, et lors de l'utilisation de la méthode de synchronisation, il peut résoudre les problèmes de sécurité du thread. Ajoutez simplement le mot clé synchronisé avant la méthode run ().
public synchronisé void run () {// ....}3. Synchroniser les blocs de code
Comme analysé ci-dessus, la résolution des problèmes de sécurité des fils nécessite uniquement de limiter l'incertitude de l'accès aux ressources partagées. Lors de l'utilisation de la méthode de synchronisation, l'ensemble du corps de la méthode devient un état d'exécution synchrone, ce qui peut provoquer une plage de synchronisation. Par conséquent, une autre méthode de synchronisation - le bloc de code de synchronisation - peut être résolu directement pour le code qui nécessite une synchronisation.
Le format du bloc de code synchrone est:
synchronisé (obj) {// ...}Parmi eux, OBJ est l'objet de verrouillage, il est donc crucial de choisir l'objet à verrouiller. D'une manière générale, cet objet de ressource partagé est sélectionné comme objet de verrouillage.
Comme dans l'exemple ci-dessus, il est préférable d'utiliser l'objet de compte comme objet Lock. (Bien sûr, il est également possible de le choisir, car le thread de création utilise la méthode Runnable. S'il s'agit d'un thread créé en héritage directement de la méthode de thread, en utilisant cet objet comme verrouillage de synchronisation ne jouera en fait aucun rôle car il s'agit d'un objet différent. Par conséquent, vous devez être très prudent lorsque vous choisissez un verrou de synchronisation ...).
4. verrouillage de synchronisation des objets en bloc
Comme nous pouvons le voir ci-dessus, précisément parce que nous devons faire si attention à la sélection des objets de verrouillage synchrones, y a-t-il une solution simple? Il peut faciliter le découplage des objets de verrouillage synchrones à partir de ressources partagées, tout en résolvant bien les problèmes de sécurité des filetages.
L'utilisation de verrous de synchronisation d'objets de verrouillage peut facilement résoudre ce problème. La seule chose à noter est que l'objet Lock doit avoir une relation un à un avec l'objet ressource. Le format général du verrou de synchronisation des objets de verrouillage est:
classe x {// Affichez l'objet qui définit le verrouillage de synchronisation de verrouillage, qui a une relation individuelle avec la ressource partagée privée Lock Final Lock = new ReentrantLock (); public void m () {// Lock Lock.lock (); // ... code qui nécessite une synchronisation de filetage // relâchez le verrouillage de verrouillage.unlock (); }}5.Wait () / notify () / notifyall () Communication de thread
Ces trois méthodes sont mentionnées dans le billet de blog "Java Résumé Series: java.lang.object". Bien que ces trois méthodes soient principalement utilisées dans le multithreading, ce sont en fait des méthodes locales dans la classe d'objets. Par conséquent, théoriquement, tout objet d'objet peut être utilisé comme ton principal de ces trois méthodes. Dans la programmation réelle et multi-lancement, ce n'est qu'en synchronisant l'objet de verrouillage pour régler ces trois méthodes que la communication entre plusieurs threads peut être terminée.
wait (): fait attendre le fil actuel et le faire entrer dans un état de blocage d'attente. Jusqu'à ce qu'un autre thread appelle la méthode notify () ou notifyall () de l'objet de verrouillage synchrone pour réveiller le thread.
notify (): réveillez un seul fil en attente de cet objet de verrouillage synchrone. Si plusieurs threads attendent cet objet de verrouillage synchrone, l'un des threads sera sélectionné pour le fonctionnement du réveil. Ce n'est que lorsque le thread actuel abandonne le verrouillage de l'objet de verrouillage synchrone que le thread éveillé peut être exécuté.
notifyall (): réveillez tous les threads en attente de cet objet de verrouillage synchrone. Ce n'est que lorsque le thread actuel abandonne le verrouillage de l'objet de verrouillage synchrone que le thread éveillé peut être exécuté.
package com.qqyumidi; public class threadtest {public static void main (String [] args) {compte compte = nouveau compte ("123456", 0); Thread drawmoneythread = new DrawMoneyThread ("Get Money Thread", compte, 700); Thread DepositMoneyThread = new DepositMoneyThread ("Save Money Thread", compte, 700); drawmoneythread.start (); dépôtmoneythread.start (); }} class DrawMoneyThread étend Thread {compte de compte privé; double montant privé; public drawmoneythread (String ThreadName, compte de compte, montant double) {super (threadName); this.account = compte; this.amount = montant; } public void run () {for (int i = 0; i <100; i ++) {compte.draw (montant, i); }}} classe DeposteMoneyThread étend Thread {compte de compte privé; double montant privé; public DepositMoneyThread (String ThreadName, compte de compte, montant double) {super (threadName); this.account = compte; this.amount = montant; } public void run () {for (int i = 0; i <100; i ++) {compte.deposit (montant, i); }}} Class Compte {Private String AccountNo; double équilibre privé; // Identifiez s'il y a déjà un dépôt dans le compte Boolean Flag privé = false; Public Account () {} public Account (String AccountNo, double bilan) {this.accountno = accountno; this.balance = équilibre; } public String getAccountno () {return accountNo; } public void setAccountNo (String Accountno) {this.accountno = accountno; } public double getBalance () {return Balance; } public void setBalance (double équilibre) {this.balance = équilibre; } / ** * Économisez de l'argent * * @param DepositAmount * / public synchronisé void dépôt (double dépôt, int i) {if (drapeau) {// quelqu'un dans le compte a déjà économisé de l'argent, et le thread actuel doit attendre pour bloquer Try {System.out.pritetln (Thread.currentThread (). attendez(); // 1 system.out.println (thread.currentThread (). GetName () + "Effectué l'opération d'attente" + "- i =" + i); } catch (InterruptedException e) {e.printStackTrace (); }} else {// Démarrer la sauvegarde de System.out.println (Thread.currentThread (). getName () + "Deposit:" + DepositAmount + "- i =" + i); setBalance (solde + dépôt); Flag = true; // Réveille d'autres threads notifyall (); // 2 essayez {thread.sleep (3000); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println (Thread.currentThread (). GetName () + "- SAVE Money-- L'exécution est terminée" + "- i =" + i); }} / ** * retirer de l'argent * * @param drawamount * / public synchronisé void draw (double drawamount, int i) {if (! Flag) {// personne dans le compte n'a encore économisé de l'argent, et le thread actuel doit attendre pour bloquer Try {System.out.println (thread.currentThread (). je); attendez(); System.out.println (thread.currentThread (). GetName () + "Execute Wait Operation" + "Exécuter l'opération d'attente" + "- i =" + i); } catch (InterruptedException e) {e.printStackTrace (); }} else {// Commencez à retirer de l'argent System.out.println (thread.currentThread (). getName () + "retirer de l'argent:" + drawamount + "- i =" + i); setBalance (getBalance () - drawamount); Flag = false; // Réveille d'autres threads notifyall (); System.out.println (thread.currentThread (). GetName () + "- retirer de l'argent - l'exécution est terminée" + "- i =" + i); // 3}}} L'exemple ci-dessus démontre l'utilisation d'attente () / notify () / notifyall (). Certains résultats de sortie sont:
Le fil de retrait monétaire commence à exécuter l'opération d'attente et à exécuter l'opération d'attente - i = 0
Économie de dépôt de filetage: 700,0 - i = 0
Économisez de l'argent-épreuve de l'argent-exécution - i = 0
Le fil d'économie d'argent doit effectuer un fonctionnement d'attente - i = 1
Le thread de retrait d'argent exécute le fonctionnement d'attente et d'attente - i = 0
Retrait en argent Fil Retrait de l'argent: 700,0 - i = 1
Fil de retrait monétaire - avec la création - Exécution - i = 1
Le fil pour retirer de l'argent doit commencer à exécuter l'opération d'attente et à exécuter l'opération d'attente - i = 2
Le fil d'économie d'argent exécute l'opération d'attente - i = 1
Économie de dépôt de filetage: 700.0 - i = 2
Économisez de l'argent-épreuve de l'argent - i = 2
Le thread de retrait exécute l'opération d'attente et exécute l'opération d'attente - i = 2
Retirer de l'argent du fil de discussion retiré de l'argent: 700,0 - i = 3
Fil de retrait en argent - avec des effectifs - Exécution - i = 3
Le fil pour retirer de l'argent doit exécuter l'opération d'attente et exécuter l'opération d'attente - i = 4
Économie de dépôt de filetage: 700.0 - i = 3
Économisez de l'argent-épreuve de l'argent-exécution - i = 3
Le fil d'économie d'argent doit effectuer un fonctionnement d'attente - i = 4
Le thread de retrait monétaire exécute l'opération d'attente et le fonctionnement d'attente - i = 4
Retirer de l'argent du fil de discussion retiré de l'argent: 700,0 - i = 5
Fil de retrait en argent - avec la création - Exécution - i = 5
Le fil pour retirer de l'argent doit commencer à effectuer un fonctionnement d'attente et à exécuter le fonctionnement d'attente - i = 6
Le fil d'économie d'argent exécute l'opération d'attente - i = 4
Économie de dépôt de filetage: 700,0 - i = 5
Économisez de l'argent-épreuve de l'argent-exécution - i = 5
Le fil d'économie d'argent doit effectuer un fonctionnement d'attente - i = 6
Le thread de retrait monétaire exécute le fonctionnement d'attente et d'attente - i = 6
Retirer de l'argent du fil de discussion retiré de l'argent: 700,0 - i = 7
Fil de retrait monétaire - avec une exécution - i = 7
Le thread de retrait monétaire commence à exécuter l'opération d'attente et à exécuter l'opération d'attente - je = 8
Le fil d'économie d'argent exécute l'opération d'attente - i = 6
Économie de dépôt de filetage: 700,0 - i = 7
Par conséquent, nous devons prêter attention aux points suivants:
1. Une fois la méthode Wait () exécutée, le thread actuel entre immédiatement dans l'état de blocage d'attente et le code suivant ne sera pas exécuté;
2. Une fois la méthode notify () / notifyall () exécutée, l'objet thread (any-notify () / all-notifyall ()) sur cet objet de verrouillage de synchronisation sera éveillé. Cependant, l'objet de verrouillage de synchronisation n'est pas libéré pour le moment. C'est-à-dire que s'il y a du code derrière notify () / notifyall (), il continuera de continuer. Seulement lorsque le thread actuel est exécuté, l'objet Lock de synchronisation sera libéré;
3. Une fois Notify () / notifyall () exécuté, s'il existe une méthode Sleep () à droite, le thread actuel entrera un état de blocage, mais le verrouillage de l'objet de synchronisation n'est pas libéré et il est toujours conservé par lui-même. Ensuite, le fil continuera d'être exécuté après une certaine période de temps, les 2 suivants;
4. Wait () / notify () / nitifyall () termine la communication ou la collaboration entre les threads en fonction de différentes verrous d'objets. Par conséquent, s'il s'agit d'un verrouillage d'objet de synchronisation différent, il perdra son sens. Dans le même temps, le verrouillage de l'objet de synchronisation est préférable de maintenir une correspondance individuelle avec l'objet de ressource partagé;
5. Lorsque le thread d'attente se réveille et s'exécute, le code de méthode Wait () qui a été exécuté la dernière fois continue d'être exécuté.
Bien sûr, l'exemple ci-dessus est relativement simple, juste pour simplement utiliser la méthode wait () / notify () / noitifyall (), mais en substance, il s'agit déjà d'un modèle de consommation producteur simple.
Série d'articles:
Explication des instances multipliées Java (i)
Explication détaillée des instances multipliées Java (II)
Explication détaillée des instances multipliées Java (III)