Si votre fondation Java est faible ou si vous n'avez pas une bonne compréhension du multithreading Java, veuillez lire cet article "Apprenez la définition, l'état et les propriétés du fil de discussion de Java Multithreading"
La synchronisation a toujours été un point difficile pour le multi-threading Java, et il est rarement utilisé lorsque nous faisons le développement d'Android, mais ce n'est pas une raison pour laquelle nous ne connaissons pas la synchronisation. J'espère que cet article pourra permettre à plus de gens de comprendre et d'appliquer la synchronisation Java.
Dans les applications multi-thread, deux threads ou plus doivent partager l'accès aux mêmes données. Cela devient généralement une condition de course si deux threads accèdent au même objet et que chaque thread appelle une méthode qui modifie l'objet.
L'exemple le plus simple des conditions de concurrence est: par exemple, les billets de train sont certains, mais il y a des fenêtres pour vendre des billets de train partout, chaque fenêtre équivaut à un seul fil, et tant de threads partagent toutes les ressources de billets de train. Et il ne peut garantir son atomicité. Si deux threads utilisent cette ressource à un moment donné, les billets de train qu'ils retirent sont les mêmes (les numéros de siège sont les mêmes), ce qui causera des problèmes aux passagers. La solution est que lorsqu'un fil veut utiliser la ressource de billets de train, nous lui donnons un verrou, et une fois le travail terminé, nous donnerons le verrou à un autre fil qui souhaite utiliser cette ressource. De cette façon, la situation ci-dessus ne se produira pas.
1. Verrouillez l'objet
Le mot clé synchronisé fournit automatiquement des verrous et des conditions connexes. Il est très pratique d'utiliser synchronisé dans la plupart des cas où des verrous explicites sont nécessaires. Cependant, lorsque nous comprenons la classe Reentrantlock et les objets conditionnels, nous pouvons mieux comprendre le mot-clé synchronisé. Reentrantlock est introduit dans Java SE 5.0. La structure du bloc de code est protégée en utilisant Reentrantlock comme suit:
mlock.lock (); essayez {...} enfin {mlock.unlock ();}Cette structure garantit qu'un seul fil entre dans la zone critique à tout moment. Une fois qu'un fil bloque l'objet de verrouillage, aucun autre fil ne peut passer l'instruction de verrouillage. Lorsque d'autres threads appellent le verrouillage, ils sont bloqués jusqu'à ce que le premier thread libère l'objet de verrouillage. Il est très nécessaire de mettre finalement l'opération de déverrouillage. Si une exception se produit dans la zone critique, le verrou doit être libéré, sinon d'autres threads seront bloqués pour toujours.
2. Objet conditionnel <br /> Lors de la saisie de la zone critique, il est constaté qu'il ne peut être exécuté qu'après remplir une certaine condition. Utilisez un objet conditionnel pour gérer les threads qui ont obtenu un verrou mais ne peuvent pas faire de travail utile. Les objets conditionnels sont également appelés variables conditionnelles.
Jetons un coup d'œil à l'exemple suivant pour voir pourquoi les objets conditionnels sont nécessaires
Supposons que dans un scénario, nous devons utiliser les transferts bancaires, nous écrivons d'abord la classe bancaire, et son constructeur doit être transféré sur le nombre de comptes et le montant des comptes.
Classe publique Banque {comptes privés doubles; Banklock de verrouillage privé; Banque publique (int n, double initialBalance) {comptes = new double [n]; Banklock = new ReentrantLock (); pour (int i = 0; i <comptes.length; i ++) {comptes [i] = initialBalance; }}}Ensuite, nous voulons retirer de l'argent et rédiger une méthode de retrait. Du cédant, est le récepteur et le montant du transfert du montant. En conséquence, nous avons constaté que le solde du cédant est insuffisant. Si d'autres fils économisent suffisamment d'argent pour le cédant, le transfert peut réussir. Cependant, ce thread a acquis le verrou, qui est exclusif, et d'autres threads ne peuvent pas acquérir le verrou pour effectuer des opérations de dépôt. C'est pourquoi nous devons introduire des objets conditionnels.
Public Void Transfer (int de, int to, int montant) {banclock.lock (); essayez {while (comptes [de] <montant) {// attendez}} enfin {banclock.unlock (); }}Un objet de verrouillage a plusieurs objets de condition connexe. Vous pouvez utiliser la méthode NewCondition pour obtenir un objet de condition. Après avoir obtenu l'objet de condition, nous appelons la méthode Await et le thread actuel est bloqué et le verrou est abandonné.
Classe publique Banque {comptes privés doubles; Banklock de verrouillage privé; état privé; Banque publique (int n, double initialBalance) {comptes = new double [n]; Banklock = new ReentrantLock (); // obtient la condition d'objet condition = banclock.newCondition (); pour (int i = 0; i <comptes.length; i ++) {comptes [i] = initialBalance; }} public void transfert (int from, int to, int montant) lève l'interrupteur enceinte {banclock.lock (); essayez {while (comptes [de] <montant) {// bloquez le thread actuel et renoncez à la condition de verrouillage.Await (); }} enfin {banclock.unlock (); }}} Le fil en attente du verrou est essentiellement différent du thread appelant la méthode Await. Une fois qu'un thread appelle la méthode Await, il entrera dans l'ensemble d'attente de cette condition. Lorsque le verrouillage est disponible, le fil ne peut pas se déverrouiller immédiatement, mais à la place, il est dans un état de blocage jusqu'à ce qu'un autre thread appelle la méthode Signalall dans la même condition. Lorsqu'un autre fil est prêt à transférer de l'argent à notre célèbre précédent, il suffit d'appeler Condition.Signalall (); Cet appel réactivera tous les threads en attente de cette condition.
Lorsqu'un thread appelle la méthode Await, il ne peut pas se réactiver et espère que d'autres threads appellent la méthode Signalall pour s'activer. Si aucun autre thread n'activait le thread d'attente, une impasse se produira. Si tous les autres threads sont bloqués et que les derniers appels de threads actifs vous attendent avant de débloquer d'autres threads, il sera également bloqué. Aucun fil ne peut débloquer d'autres threads et le programme sera suspendu.
Alors, quand Signalall sera-t-il appelé? Normalement, il devrait être avantageux d'appeler SignalLall lorsque vous attendez que la direction du thread change. Dans cet exemple, lorsqu'un solde de compte change, le fil d'attente doit avoir la possibilité de vérifier le solde.
Le transfert public de void (int de, int à, int montant) lève InterruptedException {banclock.lock (); essayez {while (comptes [de] <montant) {// bloquez le thread actuel et renoncez à la condition de verrouillage.Await (); } // opération de transfert ... condition.signalall (); } enfin {banclock.unlock (); }}Lorsque la méthode SignalLall est appelée, un thread d'attente n'est pas activé immédiatement. Il débloque simplement les threads d'attente afin que ces threads puissent obtenir l'accès à l'objet en rivalisant après que le thread actuel ait quitté la méthode synchrone. Une autre méthode est le signal, qui débloque aléatoirement un fil. Si le fil ne peut toujours pas fonctionner, il sera à nouveau bloqué. Si aucun autre filetage n'appelle à nouveau le signal, le système sera bloqué.
3. Mots-clés synchronisés
Les interfaces de verrouillage et de condition fournissent aux programmeurs un degré élevé de contrôle de verrouillage, cependant, dans la plupart des cas, un tel contrôle n'est pas requis, et un mécanisme intégré dans la langue Java peut être utilisé. À partir de la version 1.0 de Java, chaque objet de Java a un verrou interne. Si une méthode est déclarée avec le mot-clé synchronisé, le verrouillage de l'objet protégera toute la méthode. Autrement dit, pour appeler cette méthode, le thread doit obtenir le verrouillage de l'objet interne.
autrement dit,
Méthode void synchronisée publique () {}Équivalent à
Méthode publique void () {this.lock.lock (); try {} enfin {this.lock.unlock ();} Dans l'exemple bancaire ci-dessus, nous pouvons déclarer la méthode de transfert de la classe bancaire comme synchronisée au lieu d'utiliser un verrou affiché.
Il n'y a qu'une seule condition connexe pour le verrouillage de l'objet interne. L'élargissement d'attente est ajouté à un fil à l'ensemble d'attente. Notifier ou notifier les méthodes débloquer le fil d'attente. En d'autres termes, l'attente est équivalente à l'appel Condition.Await (), Notifyall est équivalent à Condition.Signalall ();
Notre exemple de méthode de transfert ci-dessus peut également être écrit comme ceci:
Le transfert de void synchronisé public (int de, int à, int montant) lève InterruptedException {while (comptes [from] <montant) {wait (); } // opération de transfert ... notifyall (); }Vous pouvez voir que l'utilisation du mot clé synchronisé pour écrire du code est beaucoup plus simple. Bien sûr, pour comprendre ce code, vous devez comprendre que chaque objet a un verrou interne et que le verrou a une condition interne. Le verrouillage gère les threads qui essaient de saisir la méthode synchronisée, et les conditions gèrent les threads qui appellent.
4. Blocking synchrone <br /> ci-dessus, nous avons dit que chaque objet Java a une serrure, et un thread peut appeler la méthode de synchronisation pour obtenir le verrou, et il existe un autre mécanisme pour obtenir le verrou. En entrant dans un bloc de synchronisation, lorsque le thread entre la forme de blocage suivante:
synchronisé (obj) {}Il a donc obtenu le verrou de l'OBJ. Jetons un coup d'œil au cours de banque
Classe publique Banque {Private Double [] Comptes; Private Object Lock = New Object (); Banque publique (int n, double initialBalance) {comptes = new double [n]; pour (int i = 0; i <comptes.length; i ++) {comptes [i] = initialBalance; }} public void transfert (int de, int to, int montant) {synchronisé (lock) {// opération de transfert ...}}}Ici, la création d'objets de verrouillage est simplement utilisée pour utiliser les verrous détenus par chaque objet Java. Parfois, les développeurs utilisent un verrou d'objet pour implémenter des opérations atomiques supplémentaires, appelés verrouillage du client. Par exemple, la classe vectorielle, ses méthodes sont synchrones. Supposons maintenant que le solde bancaire est stocké dans le vecteur
Public void Transfer (vector <rouble> comptes, int from, int to, int montant) {comptes.set (from, comptes.get (from) -amount); comptes.set (à, comptes.get (to) + montant;}Les méthodes GET et SET de la classe Vecror sont synchrones, mais cela ne nous a pas aidés. Une fois le premier appel à obtenir terminé, il est tout à fait possible qu'un thread se voit refuser le droit d'exécuter dans la méthode de transfert, de sorte que un autre thread peut avoir stocké des valeurs différentes dans le même emplacement de stockage, mais nous pouvons intercepter ce verrouillage
Public void Transfer (vector <rouble> comptes, int from, int to, int montant) {synchronisé (comptes) {comptes.set (from, comptes.get (from) -amount); comptes.set (à, comptes.get (to) + montant;}}Le verrouillage du client (blocs de code synchrone) est très fragile et n'est généralement pas recommandé. Généralement, il est préférable d'utiliser les classes fournies dans le package java.util.concurrent, telles que le blocage des files d'attente. Si la méthode de synchronisation convient à votre programme, veuillez essayer d'utiliser la méthode de synchronisation. Il peut réduire le nombre de code écrit et réduire les risques d'erreurs. Si vous devez utiliser les fonctionnalités uniques fournies par la structure de verrouillage / condition, utilisez uniquement le verrouillage / la condition.
Ce qui précède concerne cet article, j'espère qu'il sera utile à l'apprentissage de tout le monde.