palabra clave sincronizada
Sincronizado, llamamos bloqueos, se utilizan principalmente para bloquear métodos y bloques de código. Cuando se sincroniza un método o bloque de código, como máximo un hilo está ejecutando el código al mismo tiempo. Cuando múltiples subprocesos acceden al método de bloqueo/bloque de código del mismo objeto, solo un hilo se ejecuta al mismo tiempo, y los subprocesos restantes deben esperar a que el hilo actual ejecute el segmento de código antes de ejecutar. Sin embargo, los hilos restantes pueden acceder a los bloques de código no bloqueados en el objeto.
Sincronizado incluye principalmente dos métodos: método sincronizado y bloque sincronizado.
método sincronizado
Declare el método sincronizado agregando la palabra clave sincronizada a la declaración del método. como:
público sincronizado sincronizado getResult ();
El método sincronizado controla el acceso a las variables de miembros de la clase. ¿Cómo evita el control de acceso de las variables de los miembros de la clase? Sabemos que el método utiliza la palabra clave sincronizada para indicar que el método está bloqueado. Cuando cualquier hilo accede al método modificado, es necesario determinar si otros hilos son "exclusivos". Cada instancia de clase corresponde a un bloqueo. Cada método sincronizado debe llamar al bloqueo de la instancia de clase del método antes de que pueda ejecutarse. De lo contrario, el hilo al que pertenece está bloqueado. Una vez que se ejecuta el método, el bloqueo será exclusivo. El bloqueo no se lanzará hasta que regrese del método, y el hilo bloqueado puede obtener el bloqueo.
De hecho, el método sincronizado tiene defectos. Si declaramos un método grande como sincronizado, afectará en gran medida la eficiencia. Si múltiples hilos acceden a un método sincronizado, entonces solo un hilo está ejecutando el método al mismo tiempo, mientras que otros hilos deben esperar. Sin embargo, si el método no usa sincronizado, todos los hilos pueden ejecutarlo al mismo tiempo, reduciendo el tiempo de ejecución total. Entonces, si sabemos que un método no será ejecutado por múltiples hilos o no hay problema de compartir recursos, no necesitamos usar la palabra clave sincronizada. Pero si debemos usar la palabra clave sincronizada, podemos reemplazar el método sincronizado mediante un bloque de código sincronizado.
bloque sincronizado
El bloque de código sincronizado juega el mismo papel que el método sincronizado, excepto que hace que el área crítica sea lo más corta posible. En otras palabras, solo protege los datos compartidos necesarios, dejando esta operación para los bloques de código largo restantes. La sintaxis es la siguiente:
sincronizado (objeto) {// código que permite el control de acceso} Si necesitamos usar la palabra clave sincronizada de esta manera, debemos usar una referencia de objeto como parámetro. Por lo general, a menudo usamos este parámetro como este. Synchronized (this) {// código que permite el control de acceso}Existe la siguiente comprensión de sincronizado (esto):
1. Cuando dos hilos concurrentes acceden a este bloque de código sincronizado (este) sincronizado en el mismo objeto de objeto, solo se puede ejecutar un hilo dentro de una vez. Otro hilo debe esperar a que el hilo actual ejecute este bloque de código antes de que pueda ejecutar el bloque de código.
2. Sin embargo, cuando un hilo accede a un bloque de código de sincronización sincronizado (este) de un objeto, otro hilo aún puede acceder al bloque de código de sincronización no sincronizado (esto) en el objeto.
3. Es particularmente crítico que cuando un subproceso acceda a un bloque de código de sincronización sincronizado (este) de un objeto, se bloquearán otros hilos para acceder a todos los otros bloques de código de sincronización sincronizados (esto) en el objeto.
4. El tercer ejemplo también se aplica a otros bloques de código de sincronización. Es decir, cuando un hilo accede a un bloque de código de sincronización sincronizado (este) de un objeto, obtiene el bloqueo del objeto de este objeto. Como resultado, otros hilos acceden a todas las partes de código sincrónico del objeto objeto se bloqueará temporalmente.
Cerrar
Hay un principio de "ven primero, ven más tarde" en Java multithreading, es decir, quien tome la llave primero la usará primero. Sabemos que para evitar problemas con la competencia de recursos, Java utiliza mecanismos de sincronización para evitar, y los mecanismos de sincronización se controlan utilizando el concepto de cerraduras. Entonces, ¿cómo se reflejan los bloqueos en los programas Java? Aquí necesitamos descubrir dos conceptos:
¿Qué es un bloqueo? En la vida diaria, es un sellador agregado a las puertas, cajas, cajones y otros objetos, que evitan que otros miren o roben, y juega un papel protector. Lo mismo es cierto en Java. Las cerraduras juegan un papel en la protección de los objetos. Si un hilo ocupa exclusivamente un cierto recurso, ¿no quiere usar otros hilos, pero quieres usarlos? ¡Hablemos de ello cuando termine de usarlo!
En un entorno de ejecución del programa Java, el JVM necesita coordinar los datos compartidos por dos tipos de subprocesos:
1. Variables de instancia guardadas en el montón
2. Variables de clase guardadas en el área del método.
En una máquina virtual Java, cada objeto y clase se asocia lógicamente con un monitor. Para un objeto, el monitor asociado protege las variables de instancia del objeto. Para las clases, los monitores protegen las variables de clase. Si un objeto no tiene variables de instancia, o una clase no tiene variables, el monitor asociado no monitoreará nada.
Para implementar la capacidad de monitoreo exclusiva del monitor, la máquina virtual Java asocia un bloqueo para cada objeto y clase. Representa los privilegios que solo un hilo puede tener en cualquier momento. Los hilos no necesitan bloquear al acceder a variables de instancia o variables de clase. Si un hilo adquiere un bloqueo, es imposible que otros hilos adquieran el mismo bloqueo antes de que libere el bloqueo. Un hilo puede bloquear el mismo objeto varias veces. Para cada objeto, la máquina virtual Java mantiene un contador de bloqueo. Cada vez que el hilo obtiene el objeto, el contador aumenta en 1, y cada vez que se libera, el contador se reduce en 1. Cuando el valor del contador es 0, el bloqueo se libera por completo.
Los programadores de Java no necesitan agregar cerraduras por sí mismos, los bloqueos de objetos son utilizados internamente por Java Virtual Machines. En un programa Java, solo necesita usar el bloque sincronizado o el método sincronizado para marcar un área de monitoreo. Cada vez que ingrese a un área de monitoreo, la máquina virtual Java bloqueará automáticamente el objeto o la clase.
Un bloqueo simple
Cuando usamos sincronizado, usamos cerraduras como esta:
public class ThreadTest {public void test () {sincronizado (this) {// haz algo}}}Sincronized asegura que solo un hilo esté ejecutando dos veces al mismo tiempo. Aquí está el uso de bloqueo en lugar de sincronizado:
public class Threadtest {Lock Lock = New Lock (); public void test () {Lock.lock (); // hacer algo bloque.unlock (); }}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.
¿Qué está bloqueado?
Antes de esta pregunta, debemos ser claros: si la palabra clave sincronizada se agrega al método o al objeto, el bloqueo que adquiere es un objeto. En Java, cada objeto se puede usar como un bloqueo, que se refleja principalmente en los siguientes tres aspectos:
Para los métodos de sincronización, el bloqueo es el objeto de instancia actual.
Para el bloque de método de sincronización, el bloqueo es un objeto configurado en los paréntesis sincronizados.
Para los métodos de sincronización estática, el bloqueo es el objeto de clase del objeto actual.
Primero, veamos el siguiente ejemplo:
public class ThreadTest_01 implementa runnable {@Override public sincronized void run () {for (int i = 0; i <3; i ++) {system.out.println (thread.currentThread (). getName ()+"run ......"); }} public static void main (string [] args) {for (int i = 0; i <5; i ++) {new Thread (new ThreadTest_01 (), "Thread_"+i) .Start (); }}}Resultados de ejecución parcial:
Thread_2run ... Thread_2run ... Thread_4run ... Thread_4run ... Thread_3run ... Thread_3run ... Thread_3run ... Thread_3run ... Thread_2run ... Thread_4run ...
Este resultado es un poco diferente del resultado esperado (estos hilos se ejecutan por aquí). Hablando lógicamente, el método de ejecución más la palabra clave sincronizada producirá un efecto de sincronización. Estos hilos deben ejecutar el método de ejecución uno tras otro. Como se mencionó anteriormente, después de agregar palabras clave sincronizadas a un método de miembro, en realidad es un bloqueo al método miembro. El punto específico es usar el objeto en sí mismo donde el método miembro se encuentra como bloqueo del objeto. Sin embargo, en este ejemplo, tenemos nuevos objetos de 10 TreveTest, y cada hilo sostendrá el bloqueo del objeto de su propio objeto de subproceso, que definitivamente no producirá un efecto sincrónico. Entonces: si estos hilos se sincronizan, ¡los bloqueos de objetos sostenidos por estos hilos deben ser compartidos y únicos!
¿Qué objeto está sincronizado bloqueado en este momento? Lo que bloquea es llamar a este objeto de método sincrónico. Es decir, si el objeto ThreadTest ejecuta métodos de sincronización en diferentes hilos, se formará mutuamente excluyente. Lograr el efecto de la sincronización. Entonces, cambie el nuevo hilo anterior (nuevo ThreadTest_01 (), "Thread_" + I) .Start (); a nuevo hilo (ThreadTest, "Thread_" + I) .Start ();
Para los métodos de sincronización, el bloqueo es el objeto de instancia actual.
El ejemplo anterior usa el método sincronizado. Echemos un vistazo al bloque de código sincronizado:
public class Threadtest_02 extiende el hilo {bloqueo de cadena privada; nombre de cadena privada; public threadtest_02 (nombre de cadena, bloqueo de cadena) {this.name = name; this.lock = Lock; } @Override public void run () {sincronizado (bloqueo) {for (int i = 0; i <3; i ++) {system.out.println (nombre+"run ......"); }}} public static void main (string [] args) {string bloquear = new String ("test"); for (int i = 0; i <5; i ++) {new ThreadTest_02 ("ThreadTest_"+I, Lock) .Start (); }}}Resultados de ejecución:
Threadtest_0 ejecutar ... threadtest_0 ejecutar ... threadtest_0 ejecutar ... threadtest_1 run ... threadtest_1 run ... threadtest_1 ejecutar ... threadtest_1 ejecutar ... threadtest_4 run ... threadtest_4 run ... threadtest_4 run ... threadtest_3 run ... threadtest_3 run ... threadtest_3 run ... threadtest_2 ej.
En el método principal, creamos un bloqueo de objeto de cadena y asignamos este objeto a cada bloqueo de variable privada del objeto de subproceso de threadtest2. Sabemos que hay una piscina de cuerdas en Java, por lo que las variables privadas de bloqueo de estos hilos en realidad apuntan a la misma área en la memoria del montón, es decir, el área donde se almacenan las variables de bloqueo en la función principal, por lo que el bloqueo de objetos es único y compartido. Sincronización de hilo! !
El objeto de cadena de bloqueo que sincronizado se bloquea aquí.
Para el bloque de método de sincronización, el bloqueo es un objeto configurado en los paréntesis sincronizados.
public class ThreadTest_03 extiende el hilo {public sincronizado static void test () {for (int i = 0; i <3; i ++) {system.out.println (thread.currentThread (). getName ()+"run ......"); }} @Override public void run () {test (); } public static void main (string [] args) {for (int i = 0; i <5; i ++) {new ThreadTest_03 (). Start (); }}}Resultados de ejecución:
Hilo-0 ejecutar ... hilo-0 ejecución ... hilo-0 ejecutar ... hilo-4 ejecutar ... hilo-4 ejecutar ... hilo-4 ejecutar ... hilo-1 ejecución ... thread-1 run ... thread-1 run ... hilo-2 run ... hilo-2 ejecutar ... hilo-2 run ... hilt-3 run ... thread-3 run ... thread-3 run ...
En este ejemplo, el método de ejecución utiliza un método de sincronización y un método de sincronización estática. Entonces, ¿cuál es el bloqueo sincronizado aquí? Sabemos que la estática está más allá del objeto y está en el nivel de clase. Por lo tanto, un bloqueo de objeto es la instancia de clase de la clase donde se encuentra la liberación estática. Dado que en el JVM, todas las clases cargadas tienen objetos de clase únicos, el único objeto ThreadTest_03.Class en este caso. No importa cuántas instancias de la clase creamos, ¡su instancia de clase sigue siendo una! Entonces, el bloqueo del objeto es único y compartido. Sincronización de hilo! !
Para los métodos de sincronización estática, el bloqueo es el objeto de clase del objeto actual.
Si una clase define una función estática sincronizada A y una función de instancia sincronizada B, entonces el mismo objeto OBJ de esta clase no constituirá sincronización al acceder a dos métodos A y B en múltiples hilos, porque sus bloqueos son diferentes. El bloqueo del método A es el objero OBJ, mientras que el bloqueo de B es la clase a la que pertenece OBJ.
Actualización de bloqueo
Hay cuatro estados en Java: estado sin bloqueo, estado de bloqueo sesgado, estado de bloqueo liviano y estado de bloqueo de peso pesado, que gradualmente aumentará con la competencia. El bloqueo se puede actualizar, pero no se puede degradar, lo que significa que el bloqueo sesgado no se puede degradar al bloqueo sesgado después de actualizar a un bloqueo liviano. Esta estrategia de actualización de bloqueo pero no se puede rebajar es mejorar la eficiencia de obtener y liberar cerraduras. La parte principal a continuación es un resumen del blog: concurrencia (ii) sincronizada en Java SE1.6.
Giro de bloqueo
Sabemos que cuando un hilo ingresa un método de sincronización/bloque de código, si encuentra que el método/bloque de código de sincronización está ocupado por otros, esperará e ingresará un estado de bloqueo. El rendimiento de este proceso es bajo.
Al encontrar la competencia de la cerradura, o esperar las cosas, el hilo puede estar menos ansioso por ingresar al estado de bloqueo, pero espere y vea si la cerradura se libera de inmediato. Este es el giro de bloqueo. Hasta cierto punto, el giro de bloqueo puede optimizar el hilo.
Cerradura positiva
Los bloqueos positivos se utilizan principalmente para resolver el problema de rendimiento de los bloqueos sin competencia. En la mayoría de los casos, los bloqueos de bloqueo no solo no tienen competencia de múltiples subprocesos, sino que siempre se obtienen varias veces por el mismo hilo. Para hacer que el hilo adquiera cerraduras a un costo más bajo, se introducen cerraduras sesgadas. Cuando un hilo obtiene un bloqueo, el hilo puede bloquear el objeto varias veces, pero cada vez que se realiza dicha operación, algunos consumo de sobrecarga debido a la operación CAS (instrucción de comparación y intercambio de CPU). Para reducir esta sobrecarga, la cerradura tenderá a ser el primer hilo para obtenerlo. Si el bloqueo no es adquirido por otros hilos durante el siguiente proceso de ejecución, el hilo que contiene el bloqueo sesgado nunca necesitará sincronizarse nuevamente.
Cuando otros hilos intentan competir por el bloqueo sesgado, el hilo que sostiene el bloqueo sesgado libera el bloqueo.
Expansión de bloqueo
Múltiples o múltiples llamadas a cerraduras con granularidad demasiado pequeña no son tan eficientes como llamar cerraduras con un gran bloqueo de granularidad.
Bloqueo liviano
La base para el bloqueo liviano para mejorar el rendimiento de sincronización del programa es que "para la mayoría de los bloqueos, no hay competencia durante todo el ciclo de sincronización", que son datos empíricos. Un bloqueo liviano crea un espacio llamado registro de bloqueo en el marco de la pila del hilo actual, que se utiliza para almacenar el apunte actual y el estado del objeto de bloqueo. Si no hay competencia, el bloqueo liviano utiliza la operación CAS para evitar la sobrecarga del uso de mutexes, pero si hay una competencia de bloqueo, además de la sobrecarga de mutexes, la operación CAS ocurre además, por lo que en el caso de la competencia, el bloqueo liviano será más lento que el bloqueo tradicional de peso pesado tradicional.
La justicia del bloqueo
Lo contrario de la justicia es el hambre. Entonces, ¿qué es "hambre"? Si un hilo no puede obtener el tiempo de ejecución de la CPU porque otros hilos siempre están ocupando la CPU, entonces llamamos al hilo "hambriento hasta la muerte". La solución al hambre se llama "justicia": todos los hilos pueden obtener oportunidades de carrera de CPU de manera justa.
Hay varias razones principales para el hambre de hilos:
Los hilos de alta prioridad consumen el tiempo de CPU de todos los hilos de baja prioridad. Podemos establecer su prioridad individualmente para cada hilo, de 1 a 10. Cuanto más alto sea el hilo de prioridad, más tiempo lleva obtener la CPU. Para la mayoría de las aplicaciones, es mejor no cambiar su valor prioritario.
El hilo se bloquea permanentemente en un estado que espera ingresar al bloque de sincronización. El área de código síncrono de Java es un factor importante que causa hambre de hilos. Los bloques de código síncronos de Java no garantizan el orden de los hilos que ingresan. Esto significa que, en teoría, hay uno o más hilos que siempre están bloqueados al tratar de ingresar al área del código sincrónico, porque otros hilos siempre son superiores a ellos para obtener acceso, lo que resulta en que no obtenga la oportunidad de correr la CPU y se "hambrienta hasta la muerte".
El hilo está esperando un objeto que también esté esperando permanentemente la finalización. Si se están ejecutando múltiples hilos en la ejecución del método Wait (), y llamar a notificar () en él no garantiza que se despertará ningún hilo, cualquier hilo puede estar en un estado de espera continua. Por lo tanto, existe el riesgo de que un hilo de espera nunca se despierte, porque otros hilos que esperan siempre se pueden despertar.
Para resolver el problema del "hambre" de hilo, podemos usar cerraduras para lograr la equidad.
Reentrabilidad de cerraduras
Sabemos que cuando un subproceso solicita un objeto que contenga un bloqueo por otro hilo, el hilo se bloqueará, pero ¿puede tener éxito cuando el hilo solicita un objeto que contenga un bloqueo por sí mismo? La respuesta es que el éxito puede tener éxito, y la garantía del éxito es la "reingreso" de las cerraduras de hilos.
"Reentable" significa que puede obtener su propio bloqueo interno nuevamente sin bloquear. como sigue:
Padre de clase pública {Public Synchronized void Method () {// Do Something}} Public Class Child extiende el padre {Método sincronizado público sincronizado () {// hacer algo super.method (); }}
Si no es reentrante, el código anterior estará bloqueado, porque llamar al método de Child () primero adquirirá el bloqueo incorporado del padre de la clase matriz y luego adquirirá el bloqueo incorporado del niño. Al llamar al método de la clase principal, debe volver al bloqueo incorporado de la clase principal nuevamente. Si no es reentrante, puede caer en un punto muerto.
La implementación de la reentrabilidad de Java multithreading es asociar un cálculo de solicitud y un hilo que lo ocupa por cada bloqueo. Cuando el recuento es 0, se cree que la cerradura no está ocupada, y luego cualquier hilo puede obtener la propiedad de la cerradura. Cuando un subproceso solicita correctamente, el JVM registrará el hilo que mantiene el bloqueo y establecerá el recuento en 1. Si otros hilos solicitan el bloqueo, deben esperar. Cuando el hilo solicita nuevamente el bloqueo, el recuento será +1; Cuando el hilo de ocupación sale del bloque de código sincrónico, el recuento será -1, hasta que sea 0, se lanzará el bloqueo. Solo entonces otros hilos pueden tener la oportunidad de obtener propiedad de la cerradura.
bloquear y su clase de implementación
java.util.concurrent.locks proporciona un mecanismo de bloqueo muy flexible, que proporciona una interfaz y clase de marco para las condiciones de bloqueo y espera. Es diferente de la sincronización incorporada y los monitores, lo que permite más flexibilidad en el uso de bloqueo y condiciones. Su diagrama de estructura de clase es el siguiente:
Reentrantlock: un bloqueo mutex reentrante, la implementación principal de la interfaz de bloqueo.
ReentRantReadWriteLock:
ReadWriteLock: ReadWriteLock mantiene un par de cerraduras relacionadas, una para operaciones de solo lectura y el otro para operaciones de escritura.
Semaphore: un semáforo de conteo.
Condición: El propósito del bloqueo es permitir que el hilo adquiera el bloqueo y ver si se cumple una determinada condición que espera.
CyclicBarrier: una clase auxiliar sincrónica que permite que un grupo de hilos se espere mutuamente hasta que alcance un punto de barrera común.