¿Cuáles son las cerraduras de Java?
No pude responder a esta pregunta después de leer <programación concurrente de Java>, que muestra que no entiendo lo suficiente sobre el concepto de cerraduras. Así que volví a mirar el contenido del libro y de repente sentí que abrí mi frente. Parece que la mejor manera de aprender es aprender con problemas y resolverlos.
En Java, hay dos tipos principales de cerraduras: bloqueo interno sincronizado y bloqueo de pantalla java.util.concurrent.locks.lock. Pero si lo piensas cuidadosamente, parece que el resumen no es correcto. Debe ser una serie de cerraduras implementadas por bloqueos incorporados de Java y concurrente.
¿Por qué dice esto, porque en Java todo es un objeto, y Java tiene un bloqueo integrado en cada objeto, que también puede llamarse bloqueo de objeto/bloqueo interno? La operación de bloqueo relevante se completa a través de sincronizado.
Debido a los defectos en la implementación de sincronizado y la complejidad de los escenarios concurrentes, alguien ha desarrollado un bloqueo explícito, y estos bloqueos se derivan de java.util.concurrent.locks.locks. Por supuesto, se ha construido en JDK1.5 y versiones posteriores.
sincronizado
Primero, echemos un vistazo al sincronizado que se usa con más frecuencia. También se usa en mi trabajo diario. Sincronizado se utiliza para proporcionar un mecanismo de bloqueo para un determinado bloque de código. Implícitamente tendrá un bloqueo en los objetos Java. Esta cerradura se llama cerraduras intrínsecas o monitores. El hilo adquiere automáticamente este bloqueo antes de ingresar el bloque protegido por sincronizado, hasta que el bloqueo se lance automáticamente después de completar el código (o también puede ser una excepción). Las cerraduras incorporadas son mutuamente excluyentes. Un bloqueo solo se puede sostener con un hilo al mismo tiempo, lo que también conducirá a múltiples hilos, y los hilos detrás de la cerradura se bloquearán después de ser retenido. Esto permite que la seguridad del hilo del código garantice la atomicidad.
Volver a entrar en
Dado que las cerraduras incorporadas de Java son mutuamente excluyentes y los hilos posteriores causarán bloqueo, ¿qué sucede si el hilo que sostiene el bloqueo vuelve a entrar al intentar obtener el bloqueo? Por ejemplo, una de las siguientes situaciones:
public class baseclass {public sincronizado void do () {System.out.println ("es base"); }} Public Class SonClass extiende baseclass {public sincronizado void do () {System.out.println ("es hijo"); super.do (); }} SonClass son = new SONClass (); son.do ();En este momento, el método DO de la clase derivada primero mantendrá el bloqueo una vez, y luego ingresará el bloqueo nuevamente y lo mantendrá cuando llame a Super.do (). Si la cerradura es mutuamente excluyente, debe estar bloqueado en este momento.
Pero el resultado no es el caso, porque el bloqueo interno tiene la característica del reentrante, es decir, el bloqueo implementa un mecanismo reentrante, la gestión del recuento de referencias. Cuando el hilo 1 contiene el bloqueo A del objeto A, la referencia al bloqueo A se calculará agregando 1. Luego, cuando el hilo 1 adquiere el bloqueo A de nuevo, el hilo 1 todavía contiene el bloqueo A, entonces el cálculo agregará 1. Por supuesto, cada vez que salga del bloque de sincronización, se reducirá en 1 hasta que sea 0.
Algunas características de sincronizado
Cómo modificar el código
Método de modificación
public class baseclass {public sincronizado void do () {System.out.println ("es base"); }}Esto significa bloquear directamente un método, y debe obtener un bloqueo al ingresar este bloque de métodos.
Modificar bloques de código
public class BasecLass {bloque de objeto estático privado = nuevo objeto (); public void do () {sincronizado (bloqueo) {system.out.println ("es base"); }}}Aquí, el rango del bloqueo se reduce a algunos bloques de código en el método, lo que mejora la flexibilidad del bloqueo. Después de todo, el control de granularidad de la cerradura también es un problema clave para el bloqueo.
Tipo de bloqueo de objetos
A menudo veo que algunos códigos usan sincronizados en términos especiales y miro el siguiente código:
public class BasecLass {bloque de objeto estático privado = nuevo objeto (); public void do () {sincronizado (bloqueo) {}} public sincronizado void dovoid () {} public sincronizado void void dostaticvoid () {} public static void dostaticvoid () {sincronizado (baseclass.class) {}}Aquí hay cuatro situaciones: modificar el bloque de código, modificar el método, modificar el método estático y modificar el objeto de clase de Baseclass. Entonces, ¿cuáles son las diferencias en estas situaciones?
Modificar bloques de código
En este caso, creamos un bloqueo de objeto, utilizando sincronizado (bloqueo) en el código, lo que significa usar el bloqueo incorporado del objeto. En este caso, el control de bloqueo se entrega a un objeto. Por supuesto que hay otra forma de hacer esto:
public void do () {sincronizado (this) {system.out.println ("es base"); }}Usar esto significa el bloqueo del objeto actual. La llave del bloqueo incorporado también se menciona aquí. Proporciono un bloqueo para proteger este código. No importa qué hilo venga, enfrentará la misma cerradura.
Método para modificar objetos
¿Cuál es la situación con esta modificación directa? De hecho, es similar a la modificación de bloques de código, excepto que este es el bloqueo del objeto actual de forma predeterminada. De esta manera, es relativamente simple y claro escribir el código. Como se mencionó anteriormente, la diferencia entre modificar los bloques de código es principalmente la diferencia entre el control de la granularidad.
Modificar métodos estáticos
¿Hay algo diferente en los métodos estáticos? De hecho, es diferente. El bloqueo adquirido en este momento ya no es esto, y la clase señalada por este objeto es el bloqueo de clase. Debido a que la información de clase en Java se cargará en el área constante del método, el global es único. Esto en realidad proporciona un bloqueo global.
Objeto de clase de la clase modificada
Esta situación es bastante similar a la modificación de métodos estáticos, pero sigue siendo la misma razón. Este método puede proporcionar una granularidad de control más flexible.
resumen
Mediante el análisis y la comprensión de estas situaciones, podemos ver que el concepto principal principal de bloqueo incorporado es proporcionar un código con un bloqueo que pueda usarse para mutuamente excluyendo, y reproduce una función similar a un interruptor.
Java también proporciona algunas implementaciones para bloqueos incorporados. La característica principal es que Java es todos los objetos, y cada objeto tiene un bloqueo, por lo que puede elegir qué bloqueo usar de acuerdo con la situación.
java.util.concurrent.locks.lock
Miré sincronizado antes. En la mayoría de los casos, es casi suficiente. Sin embargo, el sistema se está volviendo cada vez más complejo en la programación concurrente, por lo que siempre hay muchos escenarios en los que el procesamiento sincronizado es más difícil. O como se indica en <programación concurrente de Java>, los bloqueos en concurrentes son un complemento de las cerraduras internas, que proporcionan características más avanzadas.
Análisis simple de java.util.concurrent.locks.lock
Esta interfaz abstrae la operación principal de la cerradura y, por lo tanto, permite que los bloqueos derivados del bloqueo tengan estas características básicas: incondicional, ciclable, aprobable, interrumpible. Además, las operaciones de bloqueo y desbloqueo se realizan explícitamente. Aquí está su código:
bloqueo de la interfaz pública {void lister (); void listinterruption () lanza interruptedException; boolean trylock (); Trylock booleano (mucho tiempo, unidad de tiempo de tiempo) arroja interruptedException; desbloqueo vacío (); Condición newcondition ();} Reentrante
Reentrantlock es un bloqueo reentrante, incluso el nombre es muy explícito. Reentrantlock proporciona semántica similar a sincronizado, pero Reentrantlock debe llamarse explícitamente, como:
clase pública Baseclass {bloqueo privado bloqueo = new ReentrantLock (); public void do () {Lock.lock (); intente {// ..} finalmente {Lock.unlock (); }}}Este método es bastante claro para la lectura de código, pero hay un problema, es decir, si olvida agregar intento finalmente o olvidar escribir bloqueo.unlock (), hará que el bloqueo no se libere, lo que puede conducir a algunos puntos muertos. No hay riesgo de sincronizado.
trylock
Reentrantlock implementa la interfaz de bloqueo, por lo que naturalmente tiene sus características, incluida Trylock. Trylock es intentar adquirir la cerradura. Si la cerradura ha sido ocupada por otros hilos, inmediatamente volverá falso. Si no es así, debe estar ocupado y devolver verdadero, lo que significa que se ha obtenido el bloqueo.
Otro método de Trylock contiene parámetros. La función de este método es especificar una hora, lo que significa que sigue intentando obtener el bloqueo durante este tiempo y renunciar si no se ha obtenido el tiempo.
Debido a que Trylock no siempre bloquea y espera cerraduras, puede evitar más los puntos muertos.
bloquear interrupciones
LockInterruptualmente responde a las interrupciones cuando los hilos adquieren bloqueos. Si se detecta una interrupción, el código de la capa superior se lanza una excepción de interrupción. En este caso, se proporciona un mecanismo de salida para un bloqueo de round-robin. Para comprender mejor la operación de bloqueo interrumpible, se escribió una demostración para comprenderla.
paquete com.test; import java.util.date; import java.util.concurrent.locks.reentrantlock; public class TestlockInterruptiblemente {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 ("hilo 1 final");} Catch (interropertexception e) {doprint ("hilo 1 es interrumpido.");});});});});});} Thread Thread2 = new Thread (new runnable () {@Override public void run () {try {doprint ("hilo 2 get bloquee."); Do123 (); doprint ("hilo 2 end.");} Catch (interruptedException e) {doprint ("El hilo 2 está interrumpido.");}}); thread1.setName ("Thread1"); Thread2.SetName ("Thread2"); Thread1.Start (); Pruebe {Thread.sleep (100); // Espere un tiempo para hacer que Thread1 se ejecute frente a Thread2} Catch (InterruptedException e) {E.PrintStackTrace (); } thread2.start (); } private static void do123 () lanza interruptedException {Lock.lockInterruptable (); doprint (thread.currentThread (). getName () + "está bloqueado"); intente {doprint (thread.currentThread (). getName () + "dosoming1 ...."); Thread.sleep (5000); // ¡Vaya unos segundos para facilitar la visualización del orden de los hilos doprint (hild.currentThread (). GetName () + "Dosoming2 ..."); doprint (Thread.CurrentThread (). getName () + "está terminado"); } Finalmente {Lock.unlock (); }} private static void doprint (texto de cadena) {System.out.println ((new Date ()). TOLOCALECRING () + ":" + Text); }}Hay dos hilos en el código anterior. Thread1 comienza antes que Thread2. Para ver el proceso de bloqueo, el código bloqueado se duerme durante 5 segundos, para que pueda sentir el proceso de los hilos primero y segundo que ingresan al proceso de adquisición de bloqueo. El resultado final del código anterior es el siguiente:
2016-9-28 15:12:56: Hilo 1 Obtenga bloqueo.
2016-9-28 15:12:56: Thread1 está bloqueado.
2016-9-28 15:12:56: Thread1 Dosoming1 ....
2016-9-28 15:12:56: Hilo 2 Obtenga bloqueo.
2016-9-28 15:13:01: Thread1 Dosoming2 ....
2016-9-28 15:13:01: Thread1 está terminado.
2016-9-28 15:13:01: Thread1 está descargado.
2016-9-28 15:13:01: Thread2 está bloqueado.
2016-9-28 15:13:01: Thread2 DoSousing1 ....
2016-9-28 15:13:01: Hilo 1 final.
2016-9-28 15:13:06: Thread2 DoSoMing2 ....
2016-9-28 15:13:06: Thread2 está terminado.
2016-9-28 15:13:06: Thread2 está descargado.
2016-9-28 15:13:06: Hilo 2 Fin.
Se puede ver que Thread1 obtiene el bloqueo primero, y Thread2 también obtendrá el bloqueo más tarde, pero Thread1 lo ha ocupado en este momento, por lo que Thread2 no obtiene el bloqueo hasta que Thread1 libera el bloqueo.
** Este código muestra que el hilo detrás de LockInterruptualmente para adquirir el bloqueo debe esperar a que se libere el bloqueo anterior antes de obtener el bloqueo. ** Pero todavía no hay una característica interrumpible, por lo que se agrega algún código a esto:
thread2.start (); intente {thread.sleep (1000); } Catch (InterruptedException e) {E.PrintStackTrace ();} // interrumpir el hilo2 en 1 segundo thread2.interrupt ();Después de que se inicia Thread2, llame al método de interrupción de Thread2. Ok, ejecute el código primero y vea los resultados:
2016-9-28 15:16:46: Hilo 1 Obtenga bloqueo.
2016-9-28 15:16:46: Thread1 está bloqueado.
2016-9-28 15:16:46: Thread1 Dosoming1 ....
2016-9-28 15:16:46: Hilo 2 Obtenga bloqueo.
2016-9-28 15:16:47: el hilo 2 se interrumpe. <-Responda directamente a la interrupción del hilo
2016-9-28 15:16:51: Thread1 Dosoming2 ....
2016-9-28 15:16:51: Thread1 está terminado.
2016-9-28 15:16:51: Thread1 está descargado.
2016-9-28 15:16:51: Hilo 1 final.
En comparación con el código anterior, se puede encontrar que Thread2 está esperando que Thread1 libere el bloqueo, pero Thread2 se interrumpe, y el código detrás de Thread2 no continuará ejecutándose.
ReadWriteLock
Como su nombre indica, es un bloqueo de lectura-escritura. Este tipo de escenario de aplicación de bloqueo de lectura-escritura se puede entender de esta manera. Por ejemplo, se proporciona principalmente una ola de datos para la lectura, y solo hay un número relativamente pequeño de operaciones de escritura. Si se usa un bloqueo mutex, conducirá a la competencia de bloqueo entre los hilos. Si todos pueden leerlo cuando lean, bloquee un recurso una vez que se pueda escribir. Dichos cambios resuelven bien este problema, lo que permite que la operación de lectura mejore el rendimiento de lectura sin afectar la operación de escritura.
Múltiples lectores pueden acceder a un recurso, o acceder por un escritor, y ambos no pueden realizarse simultáneamente.
Esta es la interfaz abstracta para los bloqueos de lectura y escritura, definiendo un bloqueo de lectura y un bloqueo de escritura.
Interfaz pública ReadWriteLock { /*** Devuelve el bloqueo utilizado para la lectura. * * @return el bloqueo utilizado para leer */ bloquear Readlock (); /*** Devuelve el bloqueo utilizado para escribir. * * @return el bloqueo utilizado para escribir */ bloquear WriteLock ();}Hay una implementación ReentRantReadWriteLock en JDK, que es un bloqueo de lectura-escritura reentrante. ReentRantReadWriteLock se puede construir en dos tipos: justo o injusto. Si no se especifica explícitamente durante la construcción, se creará un bloqueo no fair de forma predeterminada. En el modo de bloqueo no fair, el orden del acceso a los subprocesos es incierto, es decir, se puede romper en; Se puede degradar del escritor al lector, pero el lector no se puede actualizar al escritor.
Si es el modo de bloqueo justo, entonces la opción se entrega al hilo con el tiempo de espera más largo. Si un hilo de lectura obtiene el bloqueo y un hilo de escritura solicita el bloqueo de escritura, entonces la adquisición de bloqueo de lectura ya no se recibirá hasta que se complete la operación de escritura.
El análisis de código simple en realidad mantiene un bloqueo de sincronización en ReentRantReadWriteLock, pero se ve semánticamente como un bloqueo de lectura y un bloqueo de escritura. Echa un vistazo a su constructor:
Public ReentRantReadWriteLock (boolean fair) {sync = jair? new FairSync (): New NonfairSync (); ReaderLock = new Readlock (esto); WriterLock = new WriteLock (this);} // El constructor de LEAD bloqueo protegido Readlock (ReentRantReadWriteLock Lock) {Sync = Lock.sync;} // El constructor de Write Lock Proteged WriteLock (ReentRantReadWriteLock Lock) {Sync = Lock.sync;}Puede ver que el objeto de bloqueo de sincronización de ReentRantReadWriteLock realmente se hace referencia cuando se construye. Y esta clase de sincronización es una clase interna de ReentRantReadWriteLock. En resumen, los bloqueos de lectura/escritura se realizan a través de Sync. ¿Cómo colabora en la relación entre los dos?
// Método de bloqueo para leer bloqueo public void bloqueo () {Sync.Acquireshared (1);} // Método de bloqueo para escribir bloqueo de bloqueo public () {Sync.acquire (1);}La principal diferencia es que el bloqueo de lectura obtiene un bloqueo compartido, mientras que el bloqueo de escritura adquiere un bloqueo exclusivo. Aquí hay un punto que se puede mencionar, es decir, para garantizar que ReentRantReadWriteLock, tanto las cerraduras compartidas como las cerraduras exclusivas deben soportar los recuentos y reentrantes. Reentrantlock se almacena usando el estado, y State solo puede almacenar un valor de conformación. Para ser compatible con el problema de dos cerraduras, se divide en el número de hilos que contienen el bloqueo compartido o el número de hilos que contienen el bloqueo exclusivo o el recuento de reingreso respectivamente.
otro
Escribí un artículo grande que sentí que tardaría demasiado en escribir, y hay algunas cerraduras más útiles:
CountdownLatch
Es establecer un contador que se mantiene simultáneamente. Cuando la persona que llama llama al método de espera de CountdownLatch, se bloqueará si el contador actual no es 0. Llamar al método de liberación de CountdownLatch puede reducir el recuento hasta que la persona que llama a espera desbloquee.
Semáfono
Semaphore es una forma de autorización y licencia, como la configuración de 100 licencias, para que 100 hilos puedan contener cerraduras al mismo tiempo, y si esta cantidad excede, volverá a la falla.
Gracias por leer este artículo, espero que pueda ayudarlo. ¡Gracias por su apoyo para este sitio!