Desde Java 5, el paquete java.util.concurrent.locks contiene algunas implementaciones de bloqueo, por lo que ya no tiene que implementar sus propias cerraduras. Pero aún necesita entender cómo usar estas cerraduras.
Un bloqueo simple
Comencemos con un bloque de sincronización en Java:
Public Class Counter {private int count = 0; public int Inc () {sincronizado (this) {return ++ count; }}}Puede ver que hay un bloque de código sincronizado (este) en el método Inc (). Este bloque de código puede garantizar que solo un hilo pueda ejecutar el recuento return ++ al mismo tiempo. Aunque el código en el bloque de sincronización sincronizado puede ser más complejo, la operación simple del recuento ++ es suficiente para expresar el significado de la sincronización de subprocesos.
La siguiente clase de mostrador usa el bloqueo en lugar de sincronizado para lograr el mismo objetivo:
Public Class Counter {private Lock Lock = New Lock (); private int count = 0; public int Inc () {Lock.lock (); int newcount = ++ Count; Lock.unlock (); devolver una nueva cuenta; }}El método Lock () bloquea el objeto de instancia de bloqueo, por lo que todos los roscados que llaman al método Lock () en el objeto se bloquearán hasta que se llame al método desbloqueo () del objeto de bloqueo.
Aquí hay una implementación simple de la clase de bloqueo:
Public Class Counter {Public Class Lock {private boolean islocked = false; Public sincronizado void lock () lanza interruptedException {while (islocked) {wait (); } islocked = true; } public sincronizado void desbloock () {islocked = false; notificar(); }}Tenga en cuenta el bucle while (islocked) en el interior, que también se llama "bloqueo de giro". Cuando Islocked es verdadero, la llamada de hilo bloquea () bloquea y espera en la llamada Wait (). Para evitar que el hilo regrese de Wait () sin recibir la llamada Notify () (también llamada False Wakeup), el hilo volverá a comprobar la condición islocada para determinar si se puede continuar de forma segura o debe seguir esperando nuevamente, en lugar de pensar que el hilo puede continuar de manera segura después de ser despertada. Si Islocked es falso, el hilo actual saldrá del bucle While (Islocked) y confundirá a Islocked nuevamente en True, para que otros hilos llamen al método Lock () puedan agregar bloqueos en la instancia de bloqueo.
Cuando el hilo completa el código en la sección crítica (ubicada entre bloqueo () y desbloqueo ()), se llama a desbloqueo (). La ejecución de desbloqueo () volverá a establecer islockada en falso y notificará (despertar) uno de los hilos que han llamado a la función Wait () en el método Lock () y están en un estado de espera.
Reentrabilidad de cerraduras
El bloque de sincronización sincronizado en Java es reentrante. Esto significa que si un hilo Java ingresa al bloque de sincronización sincronizado en el código y, por lo tanto, obtiene un bloqueo en la tubería correspondiente al objeto de sincronización utilizado por el bloque de sincronización, entonces el hilo puede ingresar otro bloque de código Java sincronizado por el mismo objeto de tubería. Aquí hay un ejemplo:
public class Reentrant {public Synchronized Outer () {inner (); } public sincronizado inner () {// haz algo}}Tenga en cuenta que tanto externo () como Inner () se declaran sincronizados, que es equivalente a los bloqueos sincronizados (esto) en Java. Si un hilo llama a Outer (), no hay problema llamar a Inner () en Outer (), porque ambos métodos (bloques de código) están sincronizados por el mismo objeto de administración ("esto"). Si un rosca ya tiene un bloqueo en un objeto de tubería, tiene acceso a todos los bloques de código sincronizados por el objeto de tubería. Esto es reentrante. Un hilo puede ingresar cualquier bloque de código sincronizado por un bloqueo que ya tiene.
La implementación de bloqueo dada anteriormente no es reentrante. Si reescribimos la clase de reentrante como la siguiente, cuando el subproceso llama a Outer (), bloqueará en Lock.Lock () del método Inner ().
public class Reentrant2 {Lock Lock = New Lock (); public outer () {Lock.lock (); interno(); Lock.unlock (); } public sincronizado inner () {Lock.lock (); // hacer algo bloque.unlock (); }}El subproceso llamado externo () primero bloqueará la instancia de bloqueo y luego continuará llamando a Inner (). En el método inner (), el hilo intentará bloquear la instancia de bloqueo nuevamente, y la acción fallará (es decir, el hilo se bloqueará), porque la instancia de bloqueo ya está bloqueada en el método externo ().
Si el desbloqueo () no se llama entre bloqueo () dos veces, la segunda llamada al bloqueo se bloqueará. Después de ver la implementación de Lock (), encontrará que la razón es obvia:
Lock de clase pública {boolean islocked = false; Public sincronizado void lock () lanza interruptedException {while (islocked) {wait (); } islocked = true; } ...}Si se permite que un hilo salga, el método Lock () está determinado por las condiciones en el bucle while (bloqueo de giro). La condición del juicio actual es que la operación de bloqueo se permite solo cuando es islocked False, sin considerar qué hilo lo bloquea.
Para hacer que esta clase de bloqueo sea reentrable, necesitamos hacer un pequeño cambio:
Lock de clase pública {boolean islocked = false; Hilo LockedBy = NULL; int LockedCount = 0; Public sincronizado void lock () lanza interruptedException {hildthread = thread.currentThread (); while (islocked && lockedBy! = Callingthread) {Wait (); } islocked = true; LockedCount ++; LockedBy = CallingThread; } public sincronizado void desbloock () {if (thread.currentThread () == this.lockedby) {LockedCount--; if (LockedCount == 0) {islocked = false; notificar(); }}} ...}Tenga en cuenta que el bucle actual (bloqueo de giro) también tiene en cuenta el hilo que ha bloqueado la instancia de bloqueo. Si el objeto de bloqueo actual no está bloqueado (islocked = false), o el hilo de llamada actual ha bloqueado la instancia de bloqueo, entonces el bucle While no se ejecutará, y el hilo Llamado Llamado () puede salir del método (nota del traductor: "Deja salir del método" en la semántica actual significa que no llamará a la espera () y causa bloqueo).
Además, necesitamos registrar el número de veces que el mismo hilo bloquea repetidamente un objeto de bloqueo. De lo contrario, una llamada UNBLOCK () desbloqueará toda la cerradura, incluso si el bloqueo actual se ha bloqueado muchas veces. No queremos que el bloqueo se desbloquee hasta que la llamada de desbloqueo () alcance el número de veces que se llama a la llamada de bloqueo () correspondiente.
Ahora esta clase de bloqueo es reentrante.
La justicia del bloqueo
Los bloques sincronizados de Java no garantizan el orden en que los hilos intentan ingresarlos. Por lo tanto, si múltiples hilos continúan compitiendo para acceder al mismo bloque de sincronización sincronizado, existe el riesgo de que uno o más hilos nunca obtengan acceso; es decir, el acceso siempre se asigna a otros hilos. Esta situación se llama hambre de hilo. Para evitar este problema, las cerraduras deben lograr la justicia. Los bloqueos que se muestran en este artículo se implementan internamente con bloques de sincronización sincronizados, por lo que no se garantiza que sean justos.
Llame a desbloquear () en la declaración finalmente
Si el bloqueo se usa para proteger el área crítica, y el área crítica puede lanzar una excepción, es muy importante llamar a desbloqueo () en la declaración finalmente. Esto asegura que el objeto de bloqueo se pueda desbloquear para que otros hilos puedan continuar bloqueándolo. Aquí hay un ejemplo:
Lock.lock (); intente {// hacer código de sección crítico, // que puede lanzar una excepción} Finalmente {Lock.unlock ();}Esta estructura simple asegura que el objeto de bloqueo se pueda desbloquear cuando se lanza una excepción en el área crítica. Si no se llama desbloquear () en la instrucción Finalmente, cuando se lanza una excepción en la sección crítica, el objeto de bloqueo permanecerá en el estado bloqueado para siempre, lo que hará que todos los demás hilos llamen al bloqueo () en el objeto de bloqueo al bloquear.
Lo anterior es la información sobre el bloqueo de múltiples subprocesos Java. Continuaremos agregando información relevante en el futuro. ¡Gracias por su apoyo para este sitio!