Race Conditions And Concurrency Problems
Race conditions and concurrency problems
Cuando varios hilos acceden y modifican datos compartidos simultáneamente sin un control adecuado, pueden surgir problemas comunes en la programación concurrente. Entre ellos, destacan las condiciones de carrera, los interbloqueos, la espera activa y los problemas de visibilidad.
| Problema | Descripción | Solución |
|---|---|---|
| Condiciones de carrera | Acceso simultáneo a datos compartidos, resultando en comportamiento inconsistente. | Sincronización (synchronized, lock). |
| Interbloqueos | Hilos quedan esperando indefinidamente por recursos. | Orden consistente, tryLock, no anidar. |
| Espera activa | Un hilo consume CPU mientras espera una condición. | Usar wait() y notify(). |
| Problemas de visibilidad | Cambios a variables compartidas no son visibles para otros hilos. | Usar volatile o sincronización. |
1. Condiciones de Carrera (Race Conditions)
Una condición de carrera ocurre cuando:
- Dos o más hilos acceden a un recurso compartido al mismo tiempo.
- El resultado del programa depende del orden en que se ejecutan los hilos.
Ejemplo clásico de condición de carrera:
Imagina que tienes un contador compartido entre dos hilos:
public class Contador {
private int cuenta = 0;
public void incrementar() {
cuenta++;
}
public int getCuenta() {
return cuenta;
}
Si dos hilos ejecutan incrementar() simultáneamente, ambos pueden leer el mismo valor antes de actualizarlo, perdiendo un incremento. Por ejemplo:
- Hilo A y Hilo B leen
cuenta = 0. - Ambos incrementan el valor localmente (
cuenta = 1). - Ambos escriben
cuenta = 1al mismo tiempo, perdiendo el segundo incremento.
Solución: sincronización
Usa synchronized o un mecanismo de bloqueo como ReentrantLock:
public synchronized void incrementar() {
cuenta++;
}
2. Interbloqueos (Deadlocks)
Un interbloqueo ocurre cuando dos o más hilos quedan esperando indefinidamente porque cada uno tiene un recurso que el otro necesita.
Ejemplo de interbloqueo:
public class Interbloqueo {
private final Object recurso1 = new Object();
private final Object recurso2 = new Object();
public void metodo1() {
synchronized (recurso1) {
System.out.println("Hilo 1: Bloqueo recurso 1");
synchronized (recurso2) {
System.out.println("Hilo 1: Bloqueo recurso 2");
}
}
}
public void metodo2() {
synchronized (recurso2) {
System.out.println("Hilo 2: Bloqueo recurso 2");
synchronized (recurso1) {
System.out.println("Hilo 2: Bloqueo recurso 1");
}
}
}
}
Aquí:
- Si el Hilo 1 ejecuta
metodo1()y el Hilo 2 ejecutametodo2(), pueden quedar bloqueados esperando que el otro libere el recurso.
Soluciones para evitar interbloqueos:
- Orden de bloqueo consistente: Asegúrate de adquirir los recursos siempre en el mismo orden.
- Tiempo de espera: Usa métodos como
tryLock()con tiempo límite. - Evitar bloqueos anidados: Reduce la complejidad del código sincronizado.
3. Espera Activa (Busy Waiting)
La espera activa ocurre cuando un hilo permanece en un bucle consumiendo recursos de la CPU mientras espera por una condición.
Ejemplo de espera activa:
public class EsperaActiva {
private boolean listo = false;
public void esperar() {
while (!listo) {
// Espera activa: el hilo no hace nada útil aquí
}
}
public void setListo() {
listo = true;
}
}
Este código mantiene al hilo en ejecución sin propósito útil, desperdiciando recursos.
Solución: notificaciones
Usa wait() y notify() para suspender y reanudar los hilos de manera eficiente.
wait y notify solamente se pueden usar en bloques o objetos sincronizados. El wait y el notify se aplica sobre estos objetos.
public class SinEsperaActiva {
private boolean listo = false;
public synchronized void esperar() throws InterruptedException {
while (!listo) {
wait(); // Suspende el hilo hasta que otro lo notifique
}
}
public synchronized void setListo() {
listo = true;
notify(); // Notifica a los hilos en espera
}
}
4. Problemas de Visibilidad
Los problemas de visibilidad ocurren cuando los cambios realizados por un hilo a una variable no son visibles para otros hilos debido a la falta de sincronización.
Ejemplo de visibilidad:
public class ProblemaVisibilidad {
private boolean continuar = true;
public void ejecutar() {
while (continuar) {
// Este bucle puede no terminar nunca debido a problemas de visibilidad
}
}
public void detener() {
continuar = false;
}
}
En este caso:
- El cambio a
continuarrealizado pordetener()puede no ser visible para el hilo que ejecutaejecutar()debido a que los hilos usan diferentes cachés.
Solución: palabra clave volatile
Usa volatile para garantizar la visibilidad de cambios entre hilos.
private volatile boolean continuar = true;
O usa sincronización:
public synchronized void detener() {
continuar = false;
}