Quelles sont les serrures en java
Je n'ai pas pu répondre à cette question après avoir lu <Java Programming simultané>, ce qui montre que je ne comprends pas assez sur le concept de serrures. Alors j'ai regardé à nouveau le contenu du livre et j'ai soudainement eu l'impression d'avoir ouvert mon front. Il semble que la meilleure façon d'apprendre soit d'apprendre avec des problèmes et de les résoudre.
En Java, il existe deux principaux types de verrous: les verrouillage internes synchronisé et le verrou affichant java.util.concurrent.locks.lock. Mais si vous y pensez attentivement, il semble que le résumé ne soit pas correct. Il devrait s'agir d'une série de verrous mis en œuvre par Java Intégrée de verrous intégrés et simultanément.
Pourquoi cela dit-il, car en Java, tout est un objet, et Java a une serrure intégrée dans chaque objet, qui peut également être appelé verrouillage d'objet / verrou interne. L'opération de verrouillage pertinente est terminée par synchronisé.
En raison des défauts de l'implémentation de synchronisés et de la complexité des scénarios simultanés, quelqu'un a développé une serrure explicite, et ces verrous sont dérivés de java.util.concurrent.locks.locks. Bien sûr, il a été intégré aux versions JDK1.5 et ultérieures.
synchronisé
Tout d'abord, jetons un coup d'œil aux synchronisés qui sont utilisés plus fréquemment. Il est également utilisé dans mon travail quotidien. Synchronisé est utilisé pour fournir un mécanisme de verrouillage pour un certain bloc de code. Il aura implicitement une serrure dans les objets Java. Cette serrure est appelée serrures intrinsèques ou moniteurs. Le thread acquiert automatiquement ce verrou avant d'entrer dans le bloc protégé par synchronisé, jusqu'à ce que le verrouillage soit automatiquement libéré une fois le code terminé (ou peut également être une exception). Les verrous intégrés s'excluent mutuellement. Une serrure ne peut être maintenue que par un seul fil en même temps, ce qui conduira également à plusieurs threads, et les fils derrière le verrou seront bloqués après avoir été maintenu. Cela permet à la sécurité des fils du code d'assurer l'atomicité.
Rentrer
Étant donné que les verrous intégrés Java s'excluent mutuellement et que les threads suivants provoqueront un blocage, que se passe-t-il si le fil qui maintient le verrou est à nouveau lorsque vous essayez d'obtenir le verrou? Par exemple, l'une des situations suivantes:
classe publique Baseclass {public synchronisé void do () {System.out.println ("est base"); }} classe publique SonClass étend Baseclass {public synchronisé void do () {System.out.println ("est fils"); super.do (); }} SonClass Son = new SonClass (); Son.do ();À l'heure actuelle, la méthode DO de la classe dérivée maintiendra d'abord le verrou une fois, puis entrera à nouveau le verrou et maintiendra-le lors de l'appel super.do (). Si la serrure s'exclut mutuellement, elle devrait être dans l'impasse pour le moment.
Mais le résultat n'est pas le cas, car le verrou interne a la caractéristique de réentrante, c'est-à-dire que la serrure met en œuvre un mécanisme réentrant, la gestion du nombre de références. Lorsque le thread 1 maintient le verrouillage de l'objet A, la référence pour verrouiller A sera calculée en ajoutant 1. Ensuite, lorsque le fil 1 acquiert le verrouillage A, le thread 1 maintient toujours le verrouillage A, alors le calcul ajoutera 1. Bien sûr, chaque fois que vous quittez le bloc de synchronisation, il sera réduit de 1 jusqu'à ce qu'il soit 0.
Quelques caractéristiques de synchronisé
Comment modifier le code
Méthode de modification
classe publique Baseclass {public synchronisé void do () {System.out.println ("est base"); }}Cela signifie verrouiller directement une méthode, et vous devez obtenir un verrou lors de la saisie de ce bloc de méthode.
Modifier les blocs de code
classe publique Baseclass {Lock d'objet statique privé = nouveau objet (); public void do () {synchronisé (lock) {System.out.println ("est base"); }}}Ici, la plage de verrouillage est réduite à certains blocs de code dans la méthode, ce qui améliore la flexibilité du verrou. Après tout, le contrôle de granularité du verrou est également un problème clé pour le verrou.
Type de verrouillage d'objet
Je vois souvent que certains codes utilisent synchronisés en termes spéciaux et regardent le code suivant:
classe publique Baseclass {Lock d'objet statique privé = nouveau objet (); public void do () {synchronisé (lock) {}} public synchronisé void doVoid () {} public synchronisé statique void dostaticvoid () {} public static void dostaticvoid () {synchronisé (baseclass.class) {}}}}Il y a quatre situations ici: modifier le bloc de code, modifier la méthode, modifier la méthode statique et modifier l'objet de classe de Baseclass. Alors, quelles sont les différences dans ces situations?
Modifier les blocs de code
Dans ce cas, nous créons un verrou d'objet, en utilisant Synchronisé (verrouillage) dans le code, ce qui signifie à l'aide du verrouillage intégré de l'objet. Dans ce cas, le contrôle de verrouillage est remis à un objet. Bien sûr, il y a une autre façon de le faire:
public void do () {synchronisé (this) {System.out.println ("est base"); }}L'utilisation signifie le verrouillage de l'objet actuel. La clé du verrou intégré est également mentionnée ici. Je fournis un verrou pour protéger ce code. Peu importe le fil, il sera confronté à la même serrure.
Méthode pour modifier les objets
Quelle est la situation avec cette modification directe? En fait, il est similaire de modifier les blocs de code, sauf qu'il s'agit du verrou de l'objet actuel par défaut. De cette façon, il est relativement simple et clair d'écrire le code. Comme mentionné précédemment, la différence entre la modification des blocs de code est principalement la différence entre le contrôle de la granularité.
Modifier les méthodes statiques
Y a-t-il quelque chose de différent dans les méthodes statiques? C'est en effet différent. Le verrou acquis pour le moment n'est plus celui-ci, et la classe pointée par cet objet est le verrouillage de la classe. Étant donné que les informations de classe en Java seront chargées dans la zone constante de la méthode, le global est unique. Cela fournit en fait un verrou global.
Objet de classe de la classe modifiée
Cette situation est en fait assez similaire lors de la modification des méthodes statiques, mais c'est toujours la même raison. Cette méthode peut fournir une granularité de contrôle plus flexible.
résumé
Grâce à l'analyse et à la compréhension de ces situations, nous pouvons en fait voir que le concept principal principal de verrouillage intégré est de fournir un morceau de code avec un verrou qui peut être utilisé pour mutuellement exclusif, et joue une fonction similaire à un commutateur.
Java fournit également des implémentations pour les verrous intégrés. La caractéristique principale est que Java est tous les objets, et chaque objet a une serrure, vous pouvez donc choisir la serrure à utiliser en fonction de la situation.
java.util.concurrent.locks.lock
J'ai regardé synchronisé plus tôt. Dans la plupart des cas, c'est presque suffisant. Cependant, le système devient de plus en plus complexe dans la programmation simultanée, il y a donc toujours de nombreux scénarios où le traitement synchronisé est plus difficile. Ou comme indiqué dans <Java Programming simultané>, les verrous simultanément sont un complément aux verrous internes, offrant des fonctionnalités plus avancées.
Analyse simple de java.util.concurrent.locks.lock
Cette interface résume le fonctionnement principal de la serrure et permet donc aux verrous dérivés de la verrouillage d'avoir ces caractéristiques de base: inconditionnels, cyclables, timides, interrupables. De plus, les opérations de verrouillage et de déverrouillage sont effectuées explicitement. Voici son code:
Lock d'interface publique {void Lock (); vide lockinterrupblement () lève l'interruption de l'Exception; booléen trylock (); Boolean Trylock (longue durée, unité TimeUnit) lève InterruptedException; void unlock (); Condition newcondition ();} Reentrantlock
Reentrantlock est un verrou réentrant, même le nom est si explicite. Reentrantlock fournit une sémantique similaire à synchronisée, mais le reentrantlock doit être appelé explicitement, tel que:
classe publique Baseclass {Lock Private Lock = new ReentrantLock (); public void do () {lock.lock (); essayez {// ..} enfin {lock.unlock (); }}}Cette méthode est assez claire pour la lecture du code, mais il y a un problème, c'est-à-dire que si vous oubliez d'ajouter Essay Enfin ou oublier d'écrire Lock.Unlock (), cela entraînera la libération du verrou, ce qui peut entraîner des blocages. Il n'y a aucun risque de synchronisation.
trylock
ReentrantLock implémente l'interface de verrouillage, donc il a naturellement ses fonctionnalités, y compris Trylock. Trylock, c'est essayer d'acquérir la serrure. Si le verrou a été occupé par d'autres fils, il reviendra immédiatement faux. Si ce n'est pas le cas, il doit être occupé et de retour, ce qui signifie que la serrure a été obtenue.
Une autre méthode Trylock contient des paramètres. La fonction de cette méthode est de spécifier un temps, ce qui signifie que vous continuez à essayer d'obtenir le verrou pendant cette période et à abandonner si le temps n'a pas été obtenu.
Parce que Trylock ne bloque pas toujours et n'attend pas les serrures, cela peut éviter la survenue de blocages davantage.
lockinterrupablement
Lockinterrupablement répond aux interruptions lorsque les threads acquièrent des verrous. Si une interruption est détectée, une exception d'interruption est lancée par le code de couche supérieur. Dans ce cas, un mécanisme de sortie est prévu pour une serrure à la ronde. Afin de mieux comprendre l'opération de verrouillage interruptible, une démo a été écrite pour la comprendre.
package com.test; import java.util.date; import java.util.concurrent.locks.rentrantlock; public class testLockincally {static reentrantLock lock = new reentrantLock (); public static void main (String [] args) {thread thread1 = new thread (new Runnable () {@Override public void run () {try {doprint ("thread 1 get lock."); do123 (); doprint ("thread 1 end.");} catch (interruptedException e) {doprint ("thread 1 est interrupté.");););}}); Thread Thread2 = new Thread (new Runnable () {@Override public void run () {try {doprint ("Thread 2 Get Lock."); DO123 (); doprint ("Thread 2 end.");} Catch (interruptedException e); thread1.setName ("thread1"); thread2.setName ("thread2"); thread1.start (); essayez {thread.sleep (100); // attendez un certain temps pour faire l'exécution de Thread1 devant Thread2} Catch (InterruptedException e) {e.printStackTrace (); } thread2.start (); } private static void DO123 () lève InterruptedException {lock.lockinterruptiblement (); doprint (thread.currentThread (). getName () + "est verrouillé."); essayez {doprint (thread.currentThread (). getName () + "Dosoming1 ...."); Thread.Sleep (5000); // Ware quelques secondes pour faciliter la visualisation de l'ordre des threads doprint (thread.currentThread (). GetName () + "Dosoming2 ...."); doprint (thread.currentThread (). getName () + "est terminé."); } enfin {lock.unlock (); }} private static void doprint (chaîne text) {System.out.println ((new Date ()). TolocaleString () + ":" + texte); }}Il y a deux threads dans le code ci-dessus. Thread1 commence plus tôt que Thread2. Afin de voir le processus de verrouillage, le code verrouillé est dormi pendant 5 secondes, afin que vous puissiez sentir le processus des premier et deuxième threads entrant dans le processus d'acquisition de verrouillage. Le résultat final du code ci-dessus est le suivant:
2016-9-28 15:12:56: Thread 1 Get Lock.
2016-9-28 15:12:56: Thread1 est verrouillé.
2016-9-28 15:12:56: thread1 dosoming1 ....
2016-9-28 15:12:56: Thread 2 Get Lock.
2016-9-28 15:13:01: thread1 dosoming2 ....
2016-9-28 15:13:01: Thread1 est terminé.
2016-9-28 15:13:01: Thread1 est déchargé.
2016-9-28 15:13:01: Thread2 est verrouillé.
2016-9-28 15:13:01: Thread2 Dosoming1 ....
2016-9-28 15:13:01: Fil 1 fin.
2016-9-28 15:13:06: Thread2 Dosoming2 ....
2016-9-28 15:13:06: Thread2 est terminé.
2016-9-28 15:13:06: Thread2 est déchargé.
2016-9-28 15:13:06: Fil 2 fin.
On peut voir que Thread1 obtient d'abord le verrou et Thread2 obtiendra également le verrou plus tard, mais Thread1 l'a occupé à ce moment, donc Thread2 n'obtient pas le verrouillage jusqu'à ce que Thread1 libère le verrou.
** Ce code montre que le fil derrière Lockinterrupablement pour acquérir le verrou doit attendre que le verrou précédent soit libéré avant d'obtenir le verrou. ** Mais il n'y a pas encore de fonctionnalité interrompue, donc un code est ajouté à ceci:
thread2.start (); try {Thread.Sleep (1000); } catch (InterruptedException e) {e.printStackTrace ();} // Interruption Thread2 dans 1 seconde thread2.interrupt ();Une fois le thread2 démarré, appelez la méthode d'interruption de Thread2. OK, exécutez d'abord le code et voyez les résultats:
2016-9-28 15:16:46: Thread 1 Get Lock.
2016-9-28 15:16:46: Thread1 est verrouillé.
2016-9-28 15:16:46: thread1 dosoming1 ....
2016-9-28 15:16:46: Thread 2 Get Lock.
2016-9-28 15:16:47: le fil 2 est interrompu. <- Répondez directement à l'interruption du thread
2016-9-28 15:16:51: thread1 dosoming2 ....
2016-9-28 15:16:51: Thread1 est terminé.
2016-9-28 15:16:51: Thread1 est déchargé.
2016-9-28 15:16:51: Fil 1 fin.
Par rapport au code précédent, on peut constater que Thread2 attend que Thread1 libère le verrouillage, mais Thread2 lui-même interrompt, et le code derrière Thread2 ne continuera pas à être exécuté.
Readwritelock
Comme son nom l'indique, il s'agit d'un verrou à lecture. Ce type de scénario d'application de verrouillage en lecture peut être compris de cette manière. Par exemple, une vague de données est principalement fournie pour la lecture, et il n'y a qu'un nombre relativement faible d'opérations d'écriture. Si une serrure mutex est utilisée, elle mènera à la concurrence entre les fils. Si tout le monde peut le lire lors de la lecture, verrouillez une ressource une fois qu'elle doit être écrite. De tels changements résolvent bien ce problème, permettant à l'opération de lecture d'améliorer les performances de lecture sans affecter l'opération d'écriture.
Une ressource est accessible par plusieurs lecteurs, ou accessible par un seul écrivain, et les deux ne peuvent pas être effectués simultanément.
Il s'agit de l'interface abstraite pour les serrures de lecture et d'écriture, de définir un verrou de lecture et un verrou d'écriture.
Interface publique ReadWriteLock {/ ** * Renvoie le verrou utilisé pour la lecture. * * @return le verrou utilisé pour la lecture * / Lock readlock (); / ** * Renvoie le verrou utilisé pour l'écriture. * * @return le verrou utilisé pour l'écriture * / verrouillage writeLock ();}Il y a une implémentation ReentRanTreadWriteLock dans JDK, qui est un verrou de lecture réentrant. Reentrantreadwritelock peut être construit en deux types: juste ou injuste. S'il n'est pas spécifié explicitement pendant la construction, un verrouillage non-fair sera créé par défaut. En mode verrouillage non-fair, l'ordre d'accès au fil est incertain, c'est-à-dire qu'il peut être cambriolé; Il peut être rétrogradé de l'écrivain au lecteur, mais le lecteur ne peut pas être mis à niveau vers l'écrivain.
S'il s'agit d'un mode de verrouillage équitable, l'option est remise au fil avec le plus de temps d'attente. Si un fil de lecture obtient le verrou et un thread d'écriture demande le verrouillage d'écriture, l'acquisition de verrouillage de lecture ne sera plus reçue jusqu'à ce que l'opération d'écriture soit terminée.
L'analyse de code simple maintient en fait un verrou de synchronisation dans ReentRanTreadWriteLock, mais il ressemble sémantiquement à un verrouillage de lecture et un verrouillage d'écriture. Jetez un œil à son constructeur:
Public ReentRanTreadWriteLock (Boolean Fair) {Sync = Fair? new FairSync (): new nonfairSync (); ReaderLock = new readlock (this); writrlock = new WriteLock (this);} // Le constructeur de Read Lock Protected readlock (ReentRanTreadWriteLock Lock) {sync = lock.sync;} // Le constructeur de Write Lock Protected WriteLock (ReentRanTreadwriteLock Lock) {sync = Lock.Sync;} (ReentRanTreadwriteLock Lock) {Sync = Lock.Sync;}Vous pouvez voir que l'objet de verrouillage de synchronisation de ReentRanTreadWriteLock est en fait référencé lors de la construction. Et cette classe de synchronisation est une classe interne de ReentRanTreadWriteLock. En bref, les verrous de lecture / écriture sont tous effectués via Sync. Comment collabore-t-il à la relation entre les deux?
// Méthode de verrouillage pour la lecture de verrouillage public Void Lock () {sync.ACQUIRESHARED (1);} // Méthode de verrouillage pour écrire Lock Public Void Lock () {sync.acquire (1);}La principale différence est que le verrouillage de lecture obtient un verrou partagé, tandis que le verrouillage d'écriture acquiert un verrou exclusif. Il y a un point ici qui peut être mentionné, c'est-à-dire afin de s'assurer que Reentrantreadwritelock, les verrous partagés et les verrous exclusifs doivent prendre en charge les comptes de maintien et les réentrants. Reentrantlock est stocké à l'aide de l'état et l'état ne peut stocker qu'une seule valeur de mise en forme. Afin d'être compatible avec le problème de deux verrous, il est divisé en le nombre de threads qui maintiennent le verrou partagé ou le nombre de threads qui maintiennent respectivement le verrou exclusif ou le nombre de réintégration.
autre
J'ai écrit un grand article que j'avais l'impression qu'il faudrait trop de temps pour écrire, et il y a des serrures plus utiles:
Compte à rebours
Il s'agit de définir un compteur qui est maintenu simultanément. Lorsque l'appelant appelle la méthode Await de CountdownLatch, elle bloquera si le compteur actuel n'est pas 0. L'appel de la méthode de version de CountdownLatch peut réduire le nombre jusqu'à ce que l'appelant qui appelle attendre se débrouillera.
Sémaphone
Le sémaphore est une forme d'autorisation et de licence, comme la mise en place de 100 licences, afin que 100 threads puissent contenir des verrous en même temps, et si ce montant dépasse, il reviendra à l'échec.
Merci d'avoir lu cet article, j'espère que cela pourra vous aider. Merci pour votre soutien à ce site!