java synchronized what is thread synchronization java
Este tutorial explica la sincronización de subprocesos en Java junto con conceptos relacionados como Java Lock, Race Condition, Mutex, Java Volatile y Deadlock en Java:
En un entorno de subprocesos múltiples en el que están involucrados varios subprocesos, es probable que haya conflictos cuando más de un subproceso intente obtener el mismo recurso al mismo tiempo. Estos choques dan como resultado una 'condición de carrera' y, por lo tanto, el programa produce resultados inesperados.
Por ejemplo, dos subprocesos actualizan un solo archivo. Si un hilo T1 está en proceso de actualización de este archivo, diga alguna variable. Ahora, mientras esta actualización de T1 todavía está en progreso, digamos que el segundo hilo T2 también actualiza la misma variable. De esta forma, la variable dará resultados incorrectos.
=> Mire la serie completa de capacitación en Java aquí.
Cuando hay varios subprocesos involucrados, debemos administrar estos subprocesos de tal manera que un solo subproceso pueda acceder a un recurso a la vez. En el ejemplo anterior, el archivo al que acceden ambos subprocesos debe administrarse de tal manera que T2 no pueda acceder al archivo hasta que T1 haya terminado de acceder a él.
Esto se hace en Java usando ' Sincronización de hilos ”.
Lo que vas a aprender:
- Sincronización de subprocesos en Java
- Multihilo sin sincronización
- Multi-hilo con sincronización
- Conclusión
Sincronización de subprocesos en Java
Como Java es un lenguaje de múltiples subprocesos, la sincronización de subprocesos tiene mucha importancia en Java ya que varios subprocesos se ejecutan en paralelo en una aplicación.
Usamos palabras clave 'Sincronizado' y 'volátil' para lograr la sincronización en Java
Necesitamos sincronización cuando el objeto o recurso compartido es mutable. Si el recurso es inmutable, los subprocesos solo leerán el recurso de forma simultánea o individual.
En este caso, no necesitamos sincronizar el recurso. En este caso, JVM asegura que El código sincronizado de Java es ejecutado por un hilo a la vez .
La mayoría de las veces, el acceso simultáneo a recursos compartidos en Java puede introducir errores como 'inconsistencia de memoria' e 'interferencia de subprocesos'. Para evitar estos errores, debemos optar por la sincronización de recursos compartidos para que el acceso a estos recursos sea mutuamente excluyente.
Usamos un concepto llamado Monitores para implementar sincronización. Solo se puede acceder a un monitor mediante un hilo a la vez. Cuando un hilo se bloquea, podemos decir que el hilo ha entrado en el monitor.
Cuando un subproceso en particular accede a un monitor, el monitor se bloquea y todos los demás subprocesos que intentan ingresar al monitor se suspenden hasta que el subproceso de acceso finaliza y libera el bloqueo.
En el futuro, analizaremos la sincronización en Java en detalle en este tutorial. Ahora, analicemos algunos conceptos básicos relacionados con la sincronización en Java.
Condición de carrera en Java
En un entorno multiproceso, cuando más de un hilo intenta acceder a un recurso compartido para escribir simultáneamente, varios hilos compiten entre sí para terminar de acceder al recurso. Esto da lugar a una 'condición de carrera'.
Una cosa a considerar es que no hay ningún problema si varios subprocesos intentan acceder a un recurso compartido solo para leer. El problema surge cuando varios subprocesos acceden al mismo recurso al mismo tiempo.
Las condiciones de carrera se producen debido a una falta de sincronización adecuada de los hilos en el programa. Cuando sincronizamos correctamente los subprocesos de manera que a la vez solo un subproceso acceda al recurso y la condición de carrera deja de existir.
Entonces, ¿cómo detectamos la condición de carrera?
La mejor forma de detectar la condición de carrera es mediante la revisión del código. Como programadores, debemos revisar el código a fondo para verificar las posibles condiciones de carrera que puedan ocurrir.
Cerraduras / Monitores en Java
Ya hemos mencionado que usamos monitores o bloqueos para implementar la sincronización. El monitor o candado es una entidad interna y está asociado con cada objeto. Entonces, siempre que un hilo necesite acceder al objeto, primero debe adquirir el bloqueo o monitor de su objeto, trabajar en el objeto y luego liberar el bloqueo.
Los bloqueos en Java se verán como se muestra a continuación:
|_+_|Como se muestra arriba, tenemos un método lock () que bloquea la instancia. Todos los subprocesos que llaman al método lock () se bloquearán hasta que los conjuntos de métodos unblock () se bloqueen como bandera falsa y notifique a todos los subprocesos en espera.
Algunos consejos para recordar acerca de las cerraduras:
- En Java, cada objeto tiene un bloqueo o un monitor. Se puede acceder a este bloqueo mediante un hilo.
- A la vez, solo un hilo puede adquirir este monitor o bloquear.
- El lenguaje de programación Java proporciona una palabra clave Synchronized ’que nos permite sincronizar los hilos haciendo un bloque o método como Synchronized.
- Los recursos compartidos a los que los subprocesos necesitan acceder se mantienen en este bloque / método sincronizado.
Mutexes en Java
Ya comentamos que en un entorno multiproceso, las condiciones de carrera pueden ocurrir cuando más de un hilo intenta acceder a los recursos compartidos simultáneamente y las condiciones de carrera dan como resultado una salida inesperada.
La parte del programa que intenta acceder al recurso compartido se llama 'Sección crítica' . Para evitar la ocurrencia de condiciones de carrera, es necesario sincronizar el acceso a la sección crítica. Al sincronizar esta sección crítica, nos aseguramos de que solo un hilo pueda acceder a la sección crítica a la vez.
El tipo más simple de sincronizador es el 'mutex'. Mutex asegura que en cualquier instancia dada, solo un hilo puede ejecutar la sección crítica.
El mutex es similar al concepto de monitores o bloqueos que discutimos anteriormente. Si un hilo necesita acceder a una sección crítica, necesita adquirir el mutex. Una vez que se adquiere el mutex, el hilo accederá al código de la sección crítica y, cuando termine, lo liberará.
Mientras tanto, los otros subprocesos que están esperando para acceder a la sección crítica se bloquearán. Tan pronto como el hilo que contiene el mutex lo suelte, otro hilo entrará en la sección crítica.
lidiar con situaciones difíciles en el trabajo
Hay varias formas en las que podemos implementar un mutex en Java.
- Uso de palabras clave sincronizadas
- Usando semáforo
- Usando ReentrantLock
En este tutorial, discutiremos el primer enfoque, es decir, la sincronización. Los otros dos enfoques: Semaphore y ReentrantLock se discutirán en el próximo tutorial en el que discutiremos el paquete concurrente de Java.
Palabra clave sincronizada
Java proporciona una palabra clave 'Sincronizado' que se puede utilizar en un programa para marcar una sección Crítica. La sección crítica puede ser un bloque de código o un método completo. Por lo tanto, solo un hilo puede acceder a la sección crítica marcada por la palabra clave Sincronizada.
Podemos escribir las partes concurrentes (partes que se ejecutan simultáneamente) para una aplicación usando la palabra clave Synchronized. También nos deshacemos de las condiciones de carrera haciendo un bloque de código o un método Sincronizado.
Cuando marcamos un bloque o método sincronizado, protegemos los recursos compartidos dentro de estas entidades del acceso simultáneo y, por lo tanto, de la corrupción.
Tipos de sincronización
Hay 2 tipos de sincronización como se explica a continuación:
# 1) Sincronización de procesos
La sincronización de procesos implica la ejecución simultánea de varios procesos o subprocesos. En última instancia, alcanzan un estado en el que estos procesos o subprocesos se comprometen con una secuencia específica de acciones.
# 2) Sincronización de hilos
En Sincronización de subprocesos, más de un subproceso está intentando acceder a un espacio compartido. Los subprocesos se sincronizan de tal manera que se accede al espacio compartido solo por un subproceso a la vez.
La sincronización de procesos está fuera del alcance de este tutorial. Por lo tanto, aquí solo discutiremos la sincronización de subprocesos.
En Java, podemos usar la palabra clave sincronizada con:
- Un bloque de codigo
- Un método
Los tipos anteriores son los tipos de sincronización de subprocesos mutuamente excluyentes. La exclusión mutua evita que los subprocesos que acceden a datos compartidos interfieran entre sí.
El otro tipo de sincronización de subprocesos es la 'comunicación entre subprocesos' que se basa en la cooperación entre subprocesos. La comunicación entre hilos está fuera del alcance de este tutorial.
Antes de continuar con la sincronización de bloques y métodos, implementemos un programa Java para demostrar el comportamiento de los subprocesos cuando no hay sincronización.
Multihilo sin sincronización
El siguiente programa Java tiene varios subprocesos que no están sincronizados.
|_+_|Producción
A partir de la salida, podemos ver que como los subprocesos no están sincronizados, la salida es inconsistente. Ambos hilos comienzan y luego muestran el contador uno tras otro. Ambos hilos salen al final.
Desde el programa dado, el primer hilo debería haber salido después de mostrar los valores del contador, y luego el segundo hilo debería haber comenzado a mostrar los valores del contador.
Ahora vayamos a la sincronización y comencemos con la sincronización de bloques de código.
Bloque de código sincronizado
Un bloque sincronizado se utiliza para sincronizar un bloque de código. Este bloque suele constar de unas pocas líneas. Un bloque sincronizado se utiliza cuando no queremos sincronizar un método completo.
Por ejemplo, tenemos un método con, digamos, 75 líneas de código. De esto, solo se requieren 10 líneas de código para ser ejecutadas por un hilo a la vez. En este caso, si hacemos que todo el método esté sincronizado, será una carga para el sistema. En tales situaciones, optamos por bloques sincronizados.
El alcance del método sincronizado es siempre menor que el de un método sincronizado. Un método sincronizado bloquea un objeto de un recurso compartido que van a utilizar varios subprocesos.
La sintaxis general de un bloque sincronizado es como se muestra a continuación:
|_+_|Aquí, 'lock_object' es una expresión de referencia de objeto en la que se obtendrá el bloqueo. Entonces, siempre que un hilo quiera acceder a las declaraciones sincronizadas dentro del bloque para su ejecución, entonces debe adquirir el bloqueo en el monitor 'lock_object'.
Como ya se mencionó, la palabra clave sincronizada asegura que solo un hilo pueda adquirir un bloqueo a la vez y que todos los demás hilos tengan que esperar hasta que el hilo que sostiene el bloqueo termine y libere el bloqueo.
Nota
- Se lanza una 'NullPointerException' si el lock_object utilizado es Null.
- Si un hilo duerme mientras aún sujeta el candado, entonces el candado no se libera. Los otros subprocesos no podrán acceder al objeto compartido durante este tiempo de suspensión.
Ahora presentaremos el ejemplo anterior que ya se implementó con ligeros cambios. En el programa anterior, no sincronizamos el código. Ahora usaremos el bloque sincronizado y compararemos la salida.
Multi-hilo con sincronización
En el programa Java a continuación, usamos un bloque sincronizado. En el método de ejecución, sincronizamos el código de líneas que imprimen el contador para cada hilo.
|_+_|Producción
Ahora, la salida de este programa que usa bloque sincronizado es bastante consistente. Como se esperaba, ambos subprocesos comienzan a ejecutarse. El primer hilo terminó de mostrar los valores del contador y sale. Luego, el segundo hilo muestra los valores del contador y sale.
Método sincronizado
Analicemos el método sincronizado en esta sección. Anteriormente hemos visto que podemos declarar un bloque pequeño que consta de menos líneas de código como un bloque sincronizado. Si queremos que toda la función esté sincronizada, entonces podemos declarar un método como sincronizado.
Cuando un método se sincroniza, solo un subproceso podrá realizar una llamada a un método a la vez.
La sintaxis general para escribir un método sincronizado es:
|_+_|Al igual que un bloque sincronizado, en el caso de un método sincronizado, necesitamos un lock_object que será utilizado por los subprocesos que acceden al método sincronizado.
Para el método sincronizado, el objeto de bloqueo puede ser uno de los siguientes:
- Si el método sincronizado es estático, entonces el objeto de bloqueo viene dado por el objeto '.class'.
- Para un método no estático, el objeto de bloqueo viene dado por el objeto actual, es decir, 'este' objeto.
Una característica peculiar de la palabra clave sincronizada es que es reentrante. Esto significa que un método sincronizado puede llamar a otro método sincronizado con el mismo bloqueo. Entonces, un hilo que sostiene el candado puede acceder a otro método sincronizado sin tener que adquirir un candado diferente.
El método sincronizado se demuestra con el siguiente ejemplo.
|_+_|Producción
En el programa anterior, hemos utilizado un método sincronizado para imprimir los cuadrados de un número. El límite superior del número se pasa al método como argumento. Luego, a partir del 1, se imprimen los cuadrados de cada número hasta que se alcanza el límite superior.
cómo probar la compatibilidad entre navegadores
En la función principal, se crea la instancia del hilo. A cada instancia de hilo se le pasa un número para imprimir cuadrados.
Como se mencionó anteriormente, cuando un método a sincronizar es estático, entonces el objeto de bloqueo está involucrado en la clase y no en el objeto. Esto significa que bloquearemos la clase y no el objeto. A esto se le llama sincronización estática.
A continuación se ofrece otro ejemplo.
|_+_|Producción
En el programa anterior, imprimimos tablas de multiplicar de números. Cada número cuya tabla se imprimirá es una instancia de hilo de diferente clase de hilo. Por lo tanto, imprimimos las tablas de multiplicar de 2 y 5, por lo que tenemos dos clases de thread_one y thread_two para imprimir las tablas 2 y 5 respectivamente.
En resumen, la palabra clave sincronizada de Java realiza las siguientes funciones:
- La palabra clave sincronizada en Java garantiza el acceso mutuamente exclusivo a los recursos compartidos al proporcionar un mecanismo de bloqueo. El bloqueo también evita las condiciones de carrera.
- Usando la palabra clave sincronizada, evitamos errores de programación concurrentes en el código.
- Cuando un método o bloque se declara como sincronizado, un hilo necesita un bloqueo exclusivo para ingresar al método o bloque sincronizado. Después de realizar las acciones necesarias, el hilo libera el bloqueo y vaciará la operación de escritura. De esta forma eliminará los errores de memoria relacionados con la inconsistencia.
Volátil en Java
Una palabra clave volátil en Java se usa para hacer que las clases sean seguras para subprocesos. También usamos la palabra clave volátil para modificar el valor de la variable por diferentes hilos. Se puede usar una palabra clave volátil para declarar una variable con tipos primitivos y objetos.
En ciertos casos, se utiliza una palabra clave volátil como alternativa a la palabra clave sincronizada, pero tenga en cuenta que no sustituye a la palabra clave sincronizada.
Cuando una variable se declara volátil, su valor nunca se almacena en caché, sino que siempre se lee desde la memoria principal. Una variable volátil garantiza el pedido y la visibilidad. Aunque una variable se puede declarar como volátil, no podemos declarar clases o métodos como volátiles.
Considere el siguiente bloque de código:
|_+_|En el código anterior, la variable myvar es estática y volátil. Una variable estática se comparte entre todos los objetos de la clase. La variable volátil siempre reside en la memoria principal y nunca se almacena en caché.
Por lo tanto, solo habrá una copia de myvar en la memoria principal y todas las acciones de lectura / escritura se realizarán en esta variable desde la memoria principal. Si myvar no se declaró como volátil, entonces cada objeto de hilo tendría una copia diferente que resultaría en inconsistencias.
Algunas de las diferencias entre las palabras clave volátiles y sincronizadas se enumeran a continuación.
Palabra clave volátil | Palabra clave sincronizada |
---|---|
La palabra clave volátil se usa solo con variables. | La palabra clave sincronizada se usa con bloques de código y métodos. |
Una palabra clave volátil no puede bloquear el hilo de espera. | La palabra clave sincronizada puede bloquear el hilo en espera. |
El rendimiento del hilo se mejora con Volatile. | El rendimiento del subproceso se degrada un poco con el sincronizado. |
Las variables volátiles residen en la memoria principal. | Las construcciones sincronizadas no residen en la memoria principal. |
Volátil sincroniza una variable entre la memoria del hilo y la memoria principal a la vez. | La palabra clave sincronizada sincroniza todas las variables a la vez. |
Deadlock en Java
Hemos visto que podemos sincronizar múltiples subprocesos usando palabras clave sincronizadas y hacer que los programas sean seguros para subprocesos. Al sincronizar los subprocesos, nos aseguramos de que los subprocesos múltiples se ejecuten simultáneamente en un entorno de subprocesos múltiples.
Sin embargo, a veces se produce una situación en la que los subprocesos ya no pueden funcionar simultáneamente. En cambio, esperan sin cesar. Esto ocurre cuando un subproceso espera en un recurso y ese recurso es bloqueado por el segundo subproceso.
El segundo subproceso, por otro lado, está esperando el recurso que está bloqueado por el primer subproceso. Esta situación da lugar a un 'punto muerto' en Java.
El interbloqueo en Java se representa con la siguiente imagen.
Como podemos ver en el diagrama anterior, el hilo A ha bloqueado el recurso r1 y está esperando el recurso r2. El subproceso B, por otro lado, ha bloqueado el recurso r2 y está esperando r1.
Por lo tanto, ninguno de los hilos puede finalizar su ejecución a menos que se apoderen de los recursos pendientes. Esta situación ha resultado en un punto muerto en el que ambos subprocesos esperan interminablemente los recursos.
A continuación se muestra un ejemplo de Deadlocks en Java.
|_+_|Producción
En el programa anterior, tenemos dos recursos compartidos y dos hilos. Ambos hilos intentan acceder a los recursos compartidos uno por uno. El resultado muestra ambos subprocesos bloqueando un recurso cada uno mientras esperan los demás. De ese modo se crea una situación de punto muerto.
Aunque no podemos evitar que las situaciones de bloqueo se produzcan por completo, ciertamente podemos evitarlas tomando algunas medidas.
A continuación se enumeran los medios mediante los cuales podemos evitar los puntos muertos en Java.
# 1) Evitando bloqueos anidados
Tener bloqueos anidados es la razón más importante para tener interbloqueos. Los bloqueos anidados son los bloqueos que se asignan a varios subprocesos. Por tanto, debemos evitar dar bloqueos a más de un hilo.
# 2) Use hilo de unión
Deberíamos usar Thread.join con el tiempo máximo para que los hilos puedan usar el tiempo máximo de ejecución. Esto evitará el interbloqueo que ocurre principalmente cuando un subproceso espera continuamente a otros.
# 3) Evite el bloqueo innecesario
Debemos bloquear solo el código necesario. Tener bloqueos innecesarios para el código puede provocar interbloqueos en el programa. Como los interbloqueos pueden romper el código y obstaculizar el flujo del programa, deberíamos inclinarnos a evitar los interbloqueos en nuestros programas.
Preguntas frecuentes
P # 1) ¿Qué es la sincronización y por qué es importante?
Responder: La sincronización es el proceso de controlar el acceso de un recurso compartido a varios subprocesos. Sin sincronización, varios subprocesos pueden actualizar o cambiar el recurso compartido al mismo tiempo, lo que genera inconsistencias.
Por lo tanto, debemos asegurarnos de que, en un entorno de subprocesos múltiples, los subprocesos estén sincronizados de modo que la forma en que acceden a los recursos compartidos sea mutuamente excluyente y coherente.
P # 2) ¿Qué es la sincronización y la no sincronización en Java?
Responder: La sincronización significa que una construcción es segura para subprocesos. Esto significa que varios subprocesos no pueden acceder a la construcción (bloque de código, método, etc.) a la vez.
Las construcciones no sincronizadas no son seguras para subprocesos. Varios subprocesos pueden acceder a los métodos o bloques no sincronizados en cualquier momento. Una clase popular no sincronizada en Java es StringBuilder.
P # 3) ¿Por qué se requiere sincronización?
Responder: Cuando los procesos deben ejecutarse al mismo tiempo, necesitamos sincronización. Esto se debe a que necesitamos recursos que puedan compartirse entre muchos procesos.
Para evitar conflictos entre procesos o subprocesos para acceder a los recursos compartidos, necesitamos sincronizar estos recursos para que todos los subprocesos tengan acceso a los recursos y la aplicación también se ejecute sin problemas.
P # 4) ¿Cómo se obtiene una ArrayList sincronizada?
Responder: Podemos usar el método de lista Collections.synchronized con ArrayList como argumento para convertir ArrayList en una lista sincronizada.
P # 5) ¿HashMap está sincronizado?
comparación de herramientas de gestión de requisitos de código abierto
Responder: No, HashMap no está sincronizado, pero HashTable está sincronizado.
Conclusión
En este tutorial, hemos discutido la sincronización de hilos en detalle. Junto con él, también aprendimos sobre la palabra clave volátil y los puntos muertos en Java. La sincronización consiste en la sincronización de procesos y subprocesos.
En un entorno de subprocesos múltiples, nos preocupa más la sincronización de subprocesos. Hemos visto el enfoque de palabras clave sincronizadas de la sincronización de subprocesos aquí.
El interbloqueo es una situación en la que varios subprocesos esperan recursos sin cesar. Hemos visto el ejemplo de interbloqueos en Java junto con los métodos para evitar interbloqueos en Java.
=> Visite aquí para aprender Java desde cero.
Lectura recomendada
- Thread.Sleep () - Método Thread Sleep () en Java con ejemplos
- Hilos de Java con métodos y ciclo de vida
- Conceptos básicos de Java: sintaxis de Java, clase de Java y conceptos básicos de Java
- Multithreading en Java - Tutorial con ejemplos
- Múltiples subprocesos en C ++ con ejemplos
- Tutorial de JAVA para principiantes: más de 100 tutoriales prácticos en vídeo de Java
- Componentes Java: plataforma Java, JDK, JRE y máquina virtual Java
- Tutorial de cadenas de Java | Métodos de cadena de Java con ejemplos