Dominando la Sincronización en Java: Garantizando la Coherencia de Datos en Concurrencia
Tags: concurrenciaenjava, java, programming
Published: 2023-08-21 12:00:00

**Introducción**

¡Bienvenidos de vuelta a nuestra serie de concurrencia en Java! En los artículos anteriores, exploramos cómo se heredan los hilos y cómo crear y gestionar subprocesos de manera efectiva. Hoy, continuaremos nuestra inmersión en el mundo de la concurrencia, centrándonos en un aspecto crucial: **la sincronización**. Prepara tu brújula, ya que navegaremos a través de las olas de los problemas de concurrencia y cómo evitarlos.

**La Importancia de la Sincronización**
Cuando varios subprocesos comparten recursos o datos, es esencial sincronizarlos adecuadamente para evitar condiciones de carrera y resultados inesperados. La sincronización en Java se puede lograr utilizando palabras clave como _**synchronized**_ o mediante objetos de bloqueo explícitos.

**Uso de _synchronized_ para Sincronización**
La palabra clave _**synchronized**_ puede aplicarse a métodos o bloques de código. Esto asegura que solo un subproceso pueda ejecutar el bloque sincronizado a la vez, evitando problemas de acceso concurrente a recursos compartido.

**Ejemplo de Uso de _synchronized_**

Supongamos que tenemos una clase llamada **BankAccount** que representa una cuenta bancaria con un saldo y queremos asegurarnos de que múltiples hilos no puedan acceder al saldo al mismo tiempo para evitar problemas de concurrencia.

![class BankAccount](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ohqt3yj1am9ruwgg413.png)

En este ejemplo, los métodos _deposit_, _withdraw_ y _getBalance_ están marcados como _**synchronized**_. Esto significa que cuando un hilo está ejecutando cualquiera de estos métodos, adquiere un bloqueo en el objeto de la instancia de **BankAccount**, lo que evita que otros hilos accedan a los métodos sincronizados simultáneamente.

Ahora podemos crear múltiples hilos que interactúen con una instancia de **BankAccount** y garantizar que los métodos se ejecuten de manera segura y en orden:

![Class Main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qw4fs3n09f04dzgzb3ch.png)

En este ejemplo, los dos hilos interactúan con la misma instancia de BankAccount, pero debido al uso de synchronized, los métodos son ejecutados de manera segura y evitan posibles problemas de concurrencia, como condiciones de carrera y lecturas/escrituras incorrectas.

En Java, el método _join()_ es utilizado para esperar a que un hilo termine su ejecución antes de que el hilo actual (llamado hilo principal) continúe ejecutándose. Cuando se llama al método join() en un hilo, el hilo actual se bloquea y espera hasta que el hilo en el que se llamó al método _join()_ complete su ejecución.

En otras palabras, las líneas _thread1.join()_ y _thread2.join()_ aseguran que el hilo principal no continúe ejecutándose y llegue a la línea _System.out.println("Final balance: " + account.getBalance())_ hasta que ambos hilos thread1 y thread2 hayan terminado de ejecutarse por completo. Esto garantiza que obtengamos el saldo final de la cuenta bancaria después de que ambos hilos hayan realizado todas sus operaciones de depósito y retiro

**Objetos de Bloqueo Explícitos**
Además de _**synchronized**_, también se pueden usar objetos de bloqueo explícitos para lograr sincronización. Esto brinda un mayor control y flexibilidad sobre la sincronización.

**Ejemplo de Uso de Objeto de Bloqueo**
Utilizaremos el mismo ejemplo del banco, realizando algunas modificaciones a la clase, ahora utilizando _locks_ para asegurar que las operaciones sean ejecutadas de manera sincronizada:

![Class BankAccount Lock](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnhhn5vfdgzyylqhjzci.png)

Ahora, los hilos de depósito y retiro son creados y lanzados en paralelo, pero las operaciones en la cuenta bancaria están protegidas por los _locks_, garantizando la coherencia y la integridad de los datos compartidos.

![Class Main Lock](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/svcxlt9xqt2isfdbszpr.png)

En el contexto del ejemplo de la cuenta bancaria, las secciones críticas de código son los métodos _deposit_ y _withdraw_, ya que involucran cambios en el saldo de la cuenta. Al llamar a _balanceLock.lock()_ antes de realizar operaciones en el saldo, se asegura que estas operaciones sean atómicas y se evita que múltiples hilos accedan simultáneamente y posiblemente alteren el saldo de manera inconsistente.

**Es importante destacar** que después de que un hilo haya terminado su trabajo en la sección crítica protegida por el bloqueo, debe **liberar el bloqueo** llamando a _balanceLock.unlock()_ para permitir que otros hilos puedan adquirirlo y acceder a la sección crítica de manera segura. Si no se libera el bloqueo adecuadamente, podría resultar en bloqueos **permanentes** o problemas de concurrencia








**Evitando Condiciones de Carrera**

Una condición de carrera ocurre cuando varios subprocesos acceden y modifican un recurso compartido al mismo tiempo, lo que puede conducir a resultados inesperados. La sincronización adecuada evita estas situaciones problemáticas.

**Beneficios de la Sincronización**

- **Consistencia de Datos:** Garantiza que los datos sean consistentes y válidos en entornos concurrentes.
- **Evita Condiciones de Carrera:** Prevención de problemas causados por accesos concurrentes a recursos compartidos.
- **Protege Recursos Críticos:** Asegura que solo un subproceso pueda acceder a recursos críticos a la vez.

**Desventajas de la Sincronización**

- **Bloqueo Excesivo:** Demasiada sincronización puede resultar en bloqueos innecesarios y reducción del rendimiento.
- **Posibilidad de Deadlocks:** Una mala sincronización puede llevar a situaciones de bloqueo mutuo entre subproceso.

**Conclusiones y Continuación**
La sincronización es un pilar fundamental en la concurrencia en Java. En este artículo, hemos explorado cómo aplicar _**synchronized**_ y objetos de bloqueo para evitar condiciones de carrera y garantizar la consistencia de los datos. En el próximo artículo, continuaremos nuestro viaje, explorando cómo manejar la comunicación y la coordinación entre subprocesos.

_¡Mantente atento para seguir aprendiendo sobre cómo enfrentar los desafíos de la concurrencia en Java!_