La sincronización en Java se basa en monitores y en la palabra clave synchronized. Permite exclusión mutua y coordinación entre hilos que comparten memoria.
synchronizedsynchronized sobre él.private final Object lock = new Object();) para evitar interferencia externa.public class ContadorSeguro {
private int valor = 0;
private final Object lock = new Object();
public void incrementar() {
synchronized (lock) {
valor++;
}
}
public int leer() {
synchronized (lock) {
return valor;
}
}
public static void main(String[] args) throws InterruptedException {
ContadorSeguro contador = new ContadorSeguro();
Runnable tarea = () -> {
for (int i = 0; i < 1_000; i++) {
contador.incrementar();
}
};
Thread h1 = new Thread(tarea, "h1");
Thread h2 = new Thread(tarea, "h2");
h1.start();
h2.start();
h1.join();
h2.join();
System.out.println("Valor final: " + contador.leer()); // esperado: 2000
}
}
Contraste con una versión sin sincronización para observar condiciones de carrera y resultados inconsistentes.
public class ContadorInseguro {
private int valor = 0;
public void incrementar() {
valor++; // no sincronizado
}
public int leer() {
return valor;
}
public static void main(String[] args) throws InterruptedException {
ContadorInseguro contador = new ContadorInseguro();
Runnable tarea = () -> {
for (int i = 0; i < 1_000; i++) {
contador.incrementar();
}
};
Thread h1 = new Thread(tarea, "h1");
Thread h2 = new Thread(tarea, "h2");
h1.start();
h2.start();
h1.join();
h2.join();
System.out.println("Valor final inseguro: " + contador.leer());
}
}
El resultado varía porque valor++ no es atómico: lee, incrementa y escribe en pasos separados. Sin exclusión mutua, los hilos pueden intercalar operaciones y perder actualizaciones (race condition), generando valores finales menores a 2000 y diferentes en cada corrida.
synchronizedthis: los métodos de instancia sincronizados usan el monitor del objeto actual.this es accesible, otros códigos pueden sincronizar sobre el mismo monitor y afectar el orden.Class correspondiente.wait(), notify(), notifyAll()synchronized sobre el mismo objeto.while para chequear la condición antes y después de wait() (maneja spurious wakeups).notify() despierta un hilo; notifyAll() despierta a todos para recompetir por el lock.Spurious wakeups son despertares espurios: un hilo puede salir de wait() sin haber sido notificado ni haberse cumplido la condición; por eso se reevalúa la condición dentro de un bucle while antes de continuar.
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
public class ColaPC {
private final Queue<Integer> cola = new LinkedList<>();
private final int capacidad = 5;
public void producir(int valor) throws InterruptedException {
synchronized (this) {
while (cola.size() == capacidad) {
wait(); // espera espacio
}
cola.add(valor);
System.out.println("Producido: " + valor + " | tamaño=" + cola.size());
notifyAll(); // hay datos
}
}
public int consumir() throws InterruptedException {
synchronized (this) {
while (cola.isEmpty()) {
wait(); // espera datos
}
int valor = cola.remove();
System.out.println("Consumido: " + valor + " | tamaño=" + cola.size());
notifyAll(); // hay espacio
return valor;
}
}
public static void main(String[] args) throws InterruptedException {
ColaPC buffer = new ColaPC();
Runnable productor = () -> {
for (int i = 1; i <= 10; i++) {
try {
buffer.producir(i);
TimeUnit.MILLISECONDS.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
};
Runnable consumidor = () -> {
for (int i = 1; i <= 10; i++) {
try {
buffer.consumir();
TimeUnit.MILLISECONDS.sleep(250);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
};
Thread tProd = new Thread(productor, "productor");
Thread tCons = new Thread(consumidor, "consumidor");
tProd.start();
tCons.start();
tProd.join();
tCons.join();
System.out.println("Proceso completado");
}
}
Partes esenciales del ejemplo:
this: los métodos usan synchronized (this) para proteger la cola.while hasta que haya capacidad); el consumidor espera datos (while hasta que no esté vacío).notifyAll() despierta a productores y consumidores para recompetir por el lock tras un cambio.sleep ayudan a observar el intercalado y facilitan reproducir el patrón productor-consumidor.