Java5 ya contiene bloqueos de lectura y escritura en el paquete java.util.concurrent. Sin embargo, debemos entender los principios detrás de su implementación.
Implementación de Java de bloqueo de lectura/escritura
Primero damos una visión general de las condiciones para leer y escribir acceso a los recursos:
Leer No Hilo está haciendo la operación de escritura, y ningún hilo solicita la operación de escritura.
Ningún hilo está haciendo operaciones de lectura y escritura.
Si un hilo quiere leer un recurso, siempre que ningún hilo se escriba en el recurso y no se escriban solicitudes de hilo en el recurso. Suponemos que las solicitudes de operaciones de escritura son más importantes que las solicitudes de operaciones de lectura, por lo que debemos aumentar la prioridad de las solicitudes de escritura. Además, si las operaciones de lectura ocurren con frecuencia y no aumentamos la prioridad de las operaciones de escritura, entonces se producirá "hambre". El hilo que solicita la operación de escritura se bloqueará hasta que todos los subprocesos de lectura se desbloqueen de ReadWriteLock. Si los permisos de operación de lectura del nuevo hilo siempre están garantizados, el hilo que espera la operación de escritura continuará bloqueando, y el resultado será "hambre". Por lo tanto, la operación de lectura solo se puede garantizar que continúe cuando ningún subproceso está bloqueando ReadWriteLock para operaciones de escritura, y ningún hilo solicita que el bloqueo esté listo para las operaciones de escritura.
Cuando otros hilos no leen o escriben operaciones en el recurso compartido, un hilo puede obtener un bloqueo de escritura para el recurso compartido y luego escribir operaciones en el recurso compartido. No importa cuántos hilos soliciten bloqueos de escritura y en qué orden, a menos que desee garantizar la equidad de la solicitud de bloqueo de escritura.
De acuerdo con la descripción anterior, un bloqueo de lectura/escritura simplemente se implementa, y el código es el siguiente
clase pública readwriteLock {private int readers = 0; Private int escritores = 0; Private int Writequests = 0; public sincronizado void lockread () lanza interruptedException {while (escritores> 0 || writequests> 0) {wait (); } lectores ++; } public sincronizado void desbloockRead () {lectores--; notifyall (); } public sincronizado void LockWrite () lanza interruptedException {WriteRequests ++; while (lectores> 0 || Escritores> 0) {Wait (); } Writequests--; escritores ++; } public sincronizado void desbloockwrite () lanza interruptedException {escritores--; notifyall (); }}En la clase ReadWriteLock, Lea Leak Lock and Write Lock, cada uno tiene un método para adquirir y liberar el bloqueo.
La implementación del bloqueo de lectura está en Lockread (). Mientras ningún hilo tenga un bloqueo de escritura (escritores == 0) y ningún hilo solicite un bloqueo de escritura (Writerequests == 0), todos los hilos que desean obtener un bloqueo de lectura se pueden obtener con éxito.
La implementación del bloqueo de escritura está en LockWrite (). Cuando un hilo quiere obtener un bloqueo de escritura, primero agregará 1 al número de solicitud de bloqueo de escritura (WriteRequests ++), y luego determinará si realmente puede obtener un bloqueo de escritura. Cuando ningún hilo contiene un bloqueo de lectura (lectores == 0) y ningún hilo contiene un bloqueo de escritura (escritores == 0), puede obtener un bloqueo de escritura. No importa cuántos hilos soliciten escribir bloqueos.
Cabe señalar que en ambos desbloqueado, desbloqueado, el método notifyal se llama en lugar de notificar. Para explicar esta razón, podemos imaginar la siguiente situación:
Si un hilo está esperando para adquirir el bloqueo de lectura, y un hilo está esperando adquirir el bloqueo de escritura. Si uno de los hilos que esperan el bloqueo de lectura se despierta con el método Notify, pero debido a que todavía hay un hilo que solicita el bloqueo de escritura (Writerequests> 0), el hilo despierto ingresará al estado de bloqueo nuevamente. Sin embargo, ninguno de los hilos que esperaban el bloqueo de escritura se despertaron, como si no hubiera pasado nada (nota del traductor: pérdida de señal). Si usa el método NotifyAll, todos los subprocesos se despertarán y luego determinarán si pueden obtener el bloqueo que solicitaron.
También hay un beneficio para usar notifyall. Si múltiples hilos de lectura esperan el bloqueo de lectura y ningún hilo está esperando el bloqueo de escritura, después de llamar a desbloqueo (), todos los hilos que esperan el bloqueo de lectura pueden adquirir de inmediato el bloqueo de lectura, en lugar de solo uno a la vez.
Reingreso de bloqueo de lectura/escritura
El bloqueo de lectura/escritura implementado anteriormente no es reentrante y se bloqueará cuando un hilo que ya contiene el bloqueo de escritura solicita el bloqueo de escritura nuevamente. La razón es que ya hay un hilo de escritura, es en sí mismo. Además, considere el siguiente ejemplo:
Para hacer que ReadWriteLock sea reentrable, se deben hacer algunas mejoras. Lo siguiente manejará la reentrada del bloqueo de lectura y la reentrada del bloqueo de escritura respectivamente.
Leer Reenter de bloqueo
Para hacer el bloqueo de lectura de ReadWriteLock Reentrant, primero debemos establecer reglas para el reentrante del bloqueo de lectura:
Para asegurarse de que el bloqueo de lectura en un hilo sea reentrante, cumpla con las condiciones para obtener el bloqueo de lectura (sin solicitud de escritura o escritura) o ya mantenga el bloqueo de lectura (independientemente de si hay una solicitud de escritura o no). Para determinar si un hilo ya ha contenido un bloqueo de lectura, se puede usar un mapa para almacenar el hilo que ya ha contenido un bloqueo de lectura y la cantidad de veces que el hilo correspondiente adquiere un bloqueo de lectura. Cuando es necesario determinar si un hilo puede obtener un bloqueo de lectura, los datos almacenados en el mapa se usan para hacer un juicio. El siguiente es el código modificado de los métodos bloqueado y desbloqueado:
clase pública ReadWriteLock {Map privado <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); Private int escritores = 0; Private int Writequests = 0; public sincronizado void lockread () lanza interruptedException {Thread CallingThread = Thread.CurrentThread (); while (! CangrantReadAccess (callingthread)) {Wait (); } ReadingThreads.put (CallingThread, (getAccessCount (callarthread) + 1)); } public sincronizado void desbloockRead () {Thread CallingThread = Thread.CurrentThread (); int accessCount = getAccessCount (callarthread); if (accessCount == 1) {ReadingThreads.remove (callarthread); } else {ReadingThreads.put (CallingThread, (AccessCount -1)); } notifyAll (); } private boolean cangrantReadAccess (hilo callando) {if (escritores> 0) return false; if (isReader (callingThread) return true; if (writeRequests> 0) return false; return true;} private int getReadaccessCount (hilo de hilo callread) {integer accessCount = ReadingThreads.get (llamado); if (AccessCount == null) return 0; returnsCount.intvalue ();} privado boolean (hilo de hilo (hilo de hilo) ReadingThreads.get (CallingThread)! = NULL;En el código, podemos ver que la reingreso de los bloqueos de lectura solo se permite si ningún subproceso tiene un bloqueo de escritura. Además, los bloqueos de lectura reentrantes tienen mayor prioridad que las cerraduras de escritura.
Escribir Reenter de bloqueo
El reentrado de bloqueo de escritura solo está permitido si un hilo ya contiene un bloqueo de escritura (recupere el bloqueo de escritura). El siguiente es el código modificado de los métodos bloquear y desbloquear.
clase pública ReadWriteLock {Map privado <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); Private int WriteAccesses = 0; Private int Writequests = 0; WritingThread de hilo privado = nulo; Public sincronizado void Lockwrite () lanza interruptedException {WriteRequests ++; Thread CallingThread = Thread.CurrentThread (); while (! CangrantWriteAccess (callingthread)) {Wait (); } Writequests--; escritos de escritos ++; WritingThread = CallingThread; } public sincronizado void desbloockwrite () lanza interruptedException {writeAccesses--; if (writeAccesses == 0) {writingthread = null; } notifyAll (); } private boolean cangrantwriteAccess (hilo callando) {if (hasReaders ()) return false; if (writingthread == null) return true; if (! iswriter (callarthread)) devolver falso; devolver verdadero; } private boolean hasReaders () {return Readingthreads.size ()> 0; } private boolean iswriter (thread calcingthread) {return writingthread == callingthread; }}Preste atención a cómo lidiar con él al determinar si el hilo actual puede adquirir el bloqueo de escritura.
Lea la actualización del bloqueo para el bloqueo de escritura
A veces, queremos un hilo que tenga un bloqueo de lectura para obtener un bloqueo de escritura. Para permitir tales operaciones, se requiere que este hilo sea el único hilo con un bloqueo de lectura. WriteLock () necesita hacer algunos cambios para lograr este objetivo:
clase pública ReadWriteLock {Map privado <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); Private int WriteAccesses = 0; Private int Writequests = 0; WritingThread de hilo privado = nulo; Public sincronizado void Lockwrite () lanza interruptedException {WriteRequests ++; Thread CallingThread = Thread.CurrentThread (); while (! CangrantWriteAccess (callingthread)) {Wait (); } Writequests--; escritos de escritos ++; WritingThread = CallingThread; } public sincronizado void desbloockwrite () lanza interruptedException {writeAccesses--; if (writeAccesses == 0) {writingthread = null; } notifyAll (); } private boolean cangrantWriteAccess (hilo callando) {if (isOlyLeader (llamado thread)) return true; if (hasReaders ()) return false; if (writingthread == null) return true; if (! iswriter (callarthread)) devolver falso; devolver verdadero; } private boolean hasReaders () {return Readingthreads.size ()> 0; } private boolean iswriter (thread calcingthread) {return writingthread == callingthread; } private boolean IsonlyReader (hilo de hilo) {return Readers == 1 && readingthreads.get (callarthread)! = null; }}Ahora la clase ReadWriteLock se puede actualizar de un bloqueo de lectura a un bloqueo de escritura.
Escribe bloqueo para leer bloqueo
A veces, los hilos que tienen bloqueos de escritura también quieren obtener cerraduras de lectura. Si un hilo tiene un bloqueo de escritura, entonces, naturalmente, otros hilos no pueden tener un bloqueo de lectura o un bloqueo de escritura. Por lo tanto, no hay peligro para un hilo que tenga un bloqueo de escritura y luego obtenga un bloqueo de lectura. Solo necesitamos hacer una modificación simple al método CANGRANTREADACESS anterior:
public class ReadWriteLock {private boolean cangrantReadAccess (hilo callando) {if (iswriter (llamado thread)) return true; if (writingthread! = null) return false; if (isReader (callarthread) return true; if (writeRequests> 0) return false; return true;}}Implementación completa de Reentrant ReadWriteLock
A continuación se muestra la implementación completa de ReadWriteLock. Para facilitar la lectura y la comprensión del código, el código anterior se ha refactorizado simplemente. El código refactorizado es el siguiente.
clase pública ReadWriteLock {Map privado <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); Private int WriteAccesses = 0; Private int Writequests = 0; WritingThread de hilo privado = nulo; public sincronizado void lockread () lanza interruptedException {Thread CallingThread = Thread.CurrentThread (); while (! CangrantReadAccess (callingthread)) {Wait (); } ReadingThreads.put (CallingThread, (getReadAccessCount (callarthread) + 1)); } private boolean cangrantReadAccess (hilo llamado thread) {if (iswriter (llamado thread)) return true; if (haswriter ()) return false; if (isReader (llamado thread)) return true; if (hasWriteRequests ()) return false; devolver verdadero; } public sincronizado void desbloockRead () {Thread CallingThread = Thread.CurrentThread (); if (! isReader (llamado thread)) {lanzar nueva ilegalmonitorStateException ("llamar hilo no" + "sostiene un bloqueo de lectura en este readwriteLock"); } int accessCount = getReadAccessCount (callarthread); if (accessCount == 1) {ReadingThreads.remove (callarthread); } else {ReadingThreads.put (CallingThread, (AccessCount -1)); } notifyAll (); } public sincronizado void LockWrite () lanza interruptedException {WriteRequests ++; Thread CallingThread = Thread.CurrentThread (); while (! CangrantWriteAccess (callingthread)) {Wait (); } Writequests--; escritos de escritos ++; WritingThread = CallingThread; } public sincronizado void desbloquekWrite () lanza interruptedException {if (! iswriter (thread.currentThread ()) {throLle ilegalMonitorStateException ("Llamar hilo no" + "retener el bloqueo de escritura en este readwriteLock");} WriteAccess--; if (writeAcesses == 0) {Writingthread = NECHELOCH);} } Boolean CangrantWriteAccess (Thread CallingThread) {if (isonlyReader (callingthread)) return true; if (accessCount == null) return 0; ReadingThreads.get (CallingThread)! = NULL; } private boolean Haswriter () {return writingthread! = null; } private boolean iswriter (thread calcingthread) {return writingthread == callingthread; } private boolean haswritequests () {return this.writequests> 0; }}Llame a desbloquear () en finalmente
Al usar ReadWriteLock para proteger las zonas críticas, si la zona crítica puede lanzar una excepción, es importante llamar a ReadUnlock () y WriteUnlock () en el bloque Finalmente. Esto se hace para garantizar que ReadWriteLock se pueda desbloquear con éxito, y otros hilos pueden solicitar el bloqueo. Aquí hay un ejemplo:
Lock.lockwrite (); intente {// do el código de sección crítica, que puede lanzar una excepción} finalmente {Lock.unlockWrite ();}La estructura del código anterior puede garantizar que ReadWriteLock también se lance cuando se lanza una excepción en el área crítica. Si el método de desbloqueo no se llama en el bloque Finalmente, cuando se lanza una excepción en la sección crítica, ReadWriteLock permanecerá en el estado de bloqueo de escritura, lo que hará que todos los hilos llamen a Lockread () o LockWrite () se bloqueen. El único factor que puede volver a colocar ReadWriteLock puede ser que ReadWriteLock sea reentrante. Cuando se lanza una excepción, el hilo puede adquirir con éxito el bloqueo, luego ejecutar la sección crítica y llamar a UplockWrite () nuevamente, lo que liberará a ReadWriteLock nuevamente. Pero, ¿qué pasa si el hilo ya no adquiere la cerradura? Por lo tanto, llamar a desbloqueo en finalmente es muy importante para escribir un código robusto.
Lo anterior es la compilación de información multiproceso de Java. Continuaremos agregando información relevante en el futuro. ¡Gracias por su apoyo para este sitio web!