Java5 contient déjà des verrous en lecture et en écriture dans le package java.util.concurrent. Néanmoins, nous devons comprendre les principes de sa mise en œuvre.
Implémentation Java du verrouillage de lecture / écriture
Donnons d'abord un aperçu des conditions de lecture et d'écriture de l'accès aux ressources:
La lecture sans thread ne fait l'opération d'écriture et aucun thread ne demande l'opération d'écriture.
Aucun fil ne fait des opérations de lecture et d'écriture.
Si un thread veut lire une ressource, tant qu'aucun fil n'écrit sur la ressource et aucune demande de thread ne demande à la ressource. Nous supposons que les demandes d'opérations d'écriture sont plus importantes que les demandes d'opérations de lecture, nous devons donc augmenter la priorité des demandes d'écriture. De plus, si les opérations de lecture se produisent fréquemment et que nous n'augmenons pas la priorité des opérations d'écriture, alors la «faim» se produira. Le thread demandant l'opération d'écriture sera bloqué jusqu'à ce que tous les threads de lecture soient déverrouillés à partir de ReadWriteLock. Si les autorisations d'opération de lecture du nouveau fil sont toujours garanties, le fil en attente de l'opération d'écriture continuera de bloquer et le résultat sera "la faim". Par conséquent, l'opération de lecture ne peut être garantie que lorsqu'aucun thread n'est verrouillé ReadWriteLock pour les opérations d'écriture, et aucun thread ne demande que le verrouillage soit prêt pour les opérations d'écriture.
Lorsque d'autres threads ne lisent ni n'écrivent les opérations sur la ressource partagée, un thread peut obtenir un verrouillage d'écriture pour la ressource partagée, puis écrire des opérations sur la ressource partagée. Peu importe combien de threads demandez des verrous d'écriture et dans quel ordre, sauf si vous souhaitez assurer l'équité de la demande de verrouillage d'écriture.
Selon la description ci-dessus, un verrouillage de lecture / écriture est simplement implémenté, et le code est le suivant
classe publique ReadWriteLock {private int Readers = 0; écrivains int privés = 0; private int WriteRequests = 0; public synchronisé void lockread () lance InterruptedException {while (writers> 0 || writeRequests> 0) {wait (); } lecteurs ++; } public synchronisé void unlockread () {lecteurs--; notifyall (); } public synchronisé void LockWrite () lance InterruptedException {writeRequests ++; while (lecteurs> 0 || écrivains> 0) {wait (); } writeRequests--; écrivains ++; } public synchronisé void unlockwrite () lève InterruptedException {Writers--; notifyall (); }}Dans la classe ReadWriteLock, lisez le verrouillage et l'écriture de verrouillage ont chacun une méthode pour acquérir et libérer le verrou.
L'implémentation de Read Lock est dans Lockread (). Tant qu'aucun thread n'a de verrouillage d'écriture (écrivains == 0) et qu'aucun thread ne demande un verrouillage d'écriture (writeRequests == 0), tous les threads qui souhaitent obtenir un verrou de lecture peuvent être obtenus avec succès.
L'implémentation de Write Lock est dans LockWrite (). Lorsqu'un thread souhaite obtenir un verrouillage d'écriture, il ajoutera d'abord 1 au numéro de demande de verrouillage d'écriture (WriteRequests ++), puis déterminera s'il peut vraiment obtenir un verrouillage d'écriture. Lorsqu'aucun thread ne contient un verrou de lecture (lecteurs == 0) et qu'aucun fil ne contient un verrouillage d'écriture (écrivains == 0), vous pouvez obtenir un verrouillage d'écriture. Peu importe le nombre de threads demandent d'écrire des serrures.
Il convient de noter que dans les deux déverrouillage, déverrouiller, la méthode Notifyall est appelée au lieu de notifier. Pour expliquer cette raison, nous pouvons imaginer la situation suivante:
Si un fil attend pour acquérir le verrouillage de lecture et qu'un fil attend d'acquérir le verrouillage d'écriture. Si l'un des threads attendant le verrouillage de lecture est éveillé par la méthode de notification, mais comme il y a toujours un thread demandant le verrouillage d'écriture (WriteRequests> 0), le thread éveillé entrera à nouveau l'état de blocage. Cependant, aucun des threads n'attendant que le verrouillage d'écriture n'a été éveillé, comme si de rien ne s'était passé (note du traducteur: perte de signal). Si vous utilisez la méthode NotifyAll, tous les threads seront éveillés et détermineront ensuite s'ils peuvent obtenir le verrou en cas de demande.
Il y a également un avantage à utiliser Notifyall. Si plusieurs threads de lecture attendent le verrouillage de lecture et qu'aucun thread n'attend le verrouillage d'écriture, après avoir appelé unlockwrite (), tous les threads attendant le verrouillage de lecture peuvent immédiatement acquérir avec succès le verrou de lecture - au lieu d'une seule à la fois.
Rentrée du verrouillage de lecture / écriture
Le verrouillage de lecture / écriture implémenté ci-dessus n'est pas réentrant et sera bloqué lorsqu'un fil qui maintient déjà le verrouillage d'écriture demande à nouveau le verrouillage d'écriture. La raison en est qu'il y a déjà un fil d'écriture - c'est lui-même. Considérez également l'exemple suivant:
Afin de rendre ReadWriteLock réentable, certaines améliorations doivent y être apportées. Ce qui suit gérera la réintégration du verrouillage de lecture et la réintégration du verrouillage d'écriture respectivement.
Lire le verrouillage
Afin de rendre le verrouillage de lecture de ReadWriteLock réentrant, nous devons d'abord établir des règles pour le verrouillage de lecture réentrant:
Pour s'assurer que le verrouillage de lecture dans un fil est réentrant, soit remplir les conditions pour obtenir le verrouillage de lecture (pas d'écriture ou une demande d'écriture) ou conserver déjà le verrou de lecture (qu'il y ait ou non une demande d'écriture). Pour déterminer si un fil a déjà contenu un verrouillage de lecture, une carte peut être utilisée pour stocker le fil qui a déjà tenu un verrou de lecture et le nombre de fois que le fil correspondant acquiert un verrou de lecture. Lorsqu'il est nécessaire de déterminer si un thread peut obtenir un verrouillage de lecture, les données stockées dans la carte sont utilisées pour porter un jugement. Ce qui suit est le code modifié des méthodes Lockread et Déverrouiller:
classe publique ReadWriteLock {Private Map <Thread, Integer> ReadingThreads = new HashMap <Thread, Integer> (); écrivains int privés = 0; private int WriteRequests = 0; public synchronisé void lockread () lève InterruptedException {Thread CallingThread = Thread.currentThread (); while (! canGrantTreadAccess (CallingThread)) {wait (); } ReadingThreads.put (CallingThread, (getACCESSCOUNT (CALLATHREAD) + 1)); } public synchronisé void unlockRead () {thread CallThread = thread.currentThread (); int AccessCount = getACCESSCOUNT (CALLATHREAD); if (AccessCount == 1) {ReadingThreads.Remove (CallingThread); } else {ReadingThreads.put (CallThread, (AccessCount -1)); } notifyall (); } private booléen canGrantTreadAccess (thread CallThread) {if (writers> 0) return false; if (IsReader (CallingThread) renvoie true; if (writeRequests> 0) return false; return true;} private int getReadAccessCount (thread CallThread) {Integer AccessCount = ReadingThreads.get (CallThread); if (accessCount == null) return 0; return accessCount. ReadingThreads.get (CallingThread)! = NULL;}}}Dans le code, nous pouvons voir que la rentrée des verrous de lecture n'est autorisée que si aucun fil n'a de serrure d'écriture. De plus, les verrous en lecture réentrants ont une priorité plus élevée que les verrous d'écriture.
Écrivez un retour de verrouillage
La réintégration du verrouillage d'écriture n'est autorisée que si un thread conserve déjà un verrouillage d'écriture (regagnez le verrouillage d'écriture). Ce qui suit est le code modifié des méthodes LockWrite et Déverrouiller.
classe publique ReadWriteLock {Private Map <Thread, Integer> ReadingThreads = new HashMap <Thread, Integer> (); private int writeAccesss = 0; private int WriteRequests = 0; thread privé writingthread = null; Le public synchronisé void LockWrite () lève InterruptedException {WriteRequests ++; Thread CallingThread = Thread.currentThread (); while (! canGrantWriteAccess (CallingThread)) {wait (); } writeRequests--; writeAccesss ++; writingThread = CallingThread; } public synchronisé void unlockwrite () lève InterruptedException {writeAccesss--; if (writeAccessmes == 0) {writethread = null; } notifyall (); } private booléen canGrantWriteAccess (thread CallingThread) {if (HasReaders ()) return false; if (WritingThread == NULL) Renvoie True; if (! isWriter (CallingThread)) renvoie false; Retour Vrai; } private boolean hasreaders () {return readingthreads.size ()> 0; } private boolean isWriter (thread CallingThread) {return writethThread == CallingThread; }}Faites attention à la façon de gérer lorsque vous déterminez si le thread actuel peut acquérir le verrouillage d'écriture.
Lire la mise à niveau de verrouillage pour écrire le verrouillage
Parfois, nous voulons un fil qui a un verrou de lecture pour obtenir un verrouillage d'écriture. Pour permettre de telles opérations, ce fil doit être le seul fil avec un verrou de lecture. WriteLock () doit apporter des modifications pour atteindre cet objectif:
classe publique ReadWriteLock {Private Map <Thread, Integer> ReadingThreads = new HashMap <Thread, Integer> (); private int writeAccesss = 0; private int WriteRequests = 0; thread privé writingthread = null; Le public synchronisé void LockWrite () lève InterruptedException {WriteRequests ++; Thread CallingThread = Thread.currentThread (); while (! canGrantWriteAccess (CallingThread)) {wait (); } writeRequests--; writeAccesss ++; writingThread = CallingThread; } public synchronisé void unlockwrite () lève InterruptedException {writeAccesss--; if (writeAccessmes == 0) {writethread = null; } notifyall (); } private booléen canGrantWriteAccess (thread CallThread) {if (iSonlyReader (CallingThread)) return true; if (HasReaders ()) renvoie false; if (WritingThread == NULL) Renvoie True; if (! isWriter (CallingThread)) renvoie false; Retour Vrai; } private boolean hasreaders () {return readingthreads.size ()> 0; } private boolean isWriter (thread CallingThread) {return writethThread == CallingThread; } private booléen iSonlyReader (thread thread) {return Readers == 1 && readingthreads.get (CallThread)! = null; }}Maintenant, la classe ReadWriteLock peut être mise à niveau d'un verrouillage de lecture à un verrouillage d'écriture.
Écrivez le verrouillage pour lire le verrouillage
Parfois, les fils qui ont des verrous d'écriture veulent également obtenir des verrous en lecture. Si un fil a un verrouillage d'écriture, alors naturellement d'autres threads ne peuvent pas avoir un verrou de lecture ou un verrouillage d'écriture. Par conséquent, il n'y a aucun danger pour un fil qui a un verrouillage d'écriture puis obtient un verrou de lecture. Il nous suffit de faire une modification simple de la méthode de canagrantreadaccess ci-dessus:
classe publique readWriteLock {private boolean canGrantTreadAccess (thread CallingThread) {if (isWriter (CallingThread)) return true; if (WritingThread! = null) return false; if (IsReader (CallingThread) renvoie true; if (writeRequests> 0) return false; return true;}}Implémentation complète de Rentran ReadWritelock
Vous trouverez ci-dessous l'implémentation complète de ReadWritelock. Afin de faciliter la lecture et la compréhension du code, le code ci-dessus a simplement été refactorisé. Le code refactorisé est le suivant.
classe publique ReadWriteLock {Private Map <Thread, Integer> ReadingThreads = new HashMap <Thread, Integer> (); private int writeAccesss = 0; private int WriteRequests = 0; thread privé writingthread = null; public synchronisé void lockread () lève InterruptedException {Thread CallingThread = Thread.currentThread (); while (! canGrantTreadAccess (CallingThread)) {wait (); } ReadingThreads.put (CallingThread, (getReadAccessCount (CallThread) + 1)); } private boolean canGrantTreadAccess (thread CallThread) {if (isWriter (CallingThread)) return true; if (haswriter ()) renvoie false; if (isReader (CallingThread)) renvoie true; if (haswriteRequests ()) renvoie false; Retour Vrai; } public synchronisé void unlockRead () {thread CallThread = thread.currentThread (); if (! IsReader (CallingThread)) {Throw New illégalItorStateException ("Le thread d'appel ne fait pas" + "Maintenez un verrou de lecture sur ce readwritelock"); } int AccessCount = getReadAccessCount (CallThread); if (AccessCount == 1) {ReadingThreads.Remove (CallingThread); } else {ReadingThreads.put (CallThread, (AccessCount -1)); } notifyall (); } public synchronisé void LockWrite () lance InterruptedException {writeRequests ++; Thread CallingThread = Thread.currentThread (); while (! canGrantWriteAccess (CallingThread)) {wait (); } writeRequests--; writeAccesss ++; writingThread = CallingThread; } public synchronisé void unlockwrite () lance InterruptedException {if (! isWriter (thread.currentThread ()) {lancer un nouveau illégalonitorstateException ("le thread d'appel ne fait pas" + "tenir le verrouillage de l'écriture sur ce readwritelock");} writeAccesss -; if (writeAccesss == 0) {writingthread = null;} notyal Boolean canGrantwReActAnd (Thread CallThread) {if (IsonlyReader (CallingThread)) Return; if (AccessCount == Null) RETOURS; && ReadingThreads.get (CallThread)! = null; } private boolean haswriter () {return writingthread! = null; } private boolean isWriter (thread CallingThread) {return writethThread == CallingThread; } privé boolean haswriteRequests () {return this.writeRequests> 0; }}Appeler unlock () en enfin
Lorsque vous utilisez ReadWriteLock pour protéger les zones critiques, si la zone critique peut lancer une exception, il est important d'appeler ReadUnlock () et WriteUnlock () dans le bloc enfin. Ceci est fait pour s'assurer que ReadWriteLock peut être déverrouillé avec succès, et d'autres threads peuvent demander le verrou. Voici un exemple:
lock.lockwrite (); essayez {// faire du code de section critique, qui peut lancer l'exception} enfin {lock.unlockwrite ();}La structure de code ci-dessus peut garantir que ReadWriteLock sera également publié lorsqu'une exception sera lancée dans le domaine critique. Si la méthode de déverrouillage n'est pas appelée dans le bloc final, lorsqu'une exception est lancée dans la section critique, ReadWriteLock restera dans l'état de verrouillage de l'écriture, ce qui provoquera un blocage de tous les threads appelant Lockread () ou LockWrite (). Le seul facteur qui peut réintégrer ReadWritelock peut être que ReadWriteLock est réentrant. Lorsqu'une exception est lancée, le fil peut acquérir avec succès le verrou, puis exécuter la section critique et appeler unlockwrite () à nouveau, qui relâchera à nouveau ReadWriteLock. Mais que se passe-t-il si le fil n'acquiert plus la serrure? Par conséquent, appeler unlockwrite dans Enfin est très important pour écrire un code robuste.
Ce qui précède est une compilation d'informations multipliées Java. Nous continuerons d'ajouter des informations pertinentes à l'avenir. Merci pour votre soutien pour ce site Web!