Fundamentos de Hardware y Memoria

Concurrencia vs Paralelismo y el JMM (Stack vs Heap)

Concurrencia es gestionar múltiples tareas intercalando tiempo. Paralelismo es ejecutar tareas simultáneamente en distintos núcleos físicos. En el JMM cada hilo tiene su Stack privado, pero comparten el Heap.

Procesador (Hardware)
CPU Core 0
L1 / L2 Cache
Hilo-1 (En ejecución)
CPU Core 1
L1 / L2 Cache
Inactivo (Modo 1 Core)
Memoria JVM (Java Memory Model)
Stack Hilo-1
int x = 10;
ref -> 0x4F10
Heap Compartido
Objeto en 0x4F10 class Contador { count = 42; }
⚠️ Zona Crítica de Conflicto
Stack Hilo-2
int y = 99;
ref -> 0x4F10
Peligros de la Concurrencia

Anatomía de una Condición de Carrera: count++

count++ parece una sola instrucción pero en bytecode son 3 pasos no atómicos: ILOAD (leer), IADD (incrementar) e ISTORE (escribir).

T0: Variable count = 5 en memoria compartida
Hilo 1 (Thread-1)
1. ILOAD count (Lee 5 a registro local)
2. IADD 1 (Calcula 5 + 1 = 6)
3. ISTORE count (Escribe 6 en Heap)
Valor en Heap 5
Esperado: 7 ...
Hilo 2 (Thread-2)
1. ILOAD count (¡Lee 5 antes de que H1 escriba!)
2. IADD 1 (Calcula 5 + 1 = 6)
3. ISTORE count (Sobrescribe con 6)
Mecanismos de Sincronización

El Cerrojo Intrínseco (synchronized) vs Atómicos Hardware (CAS)

synchronized suspende hilos en el monitor del sistema operativo (pesado). AtomicInteger usa Compare-And-Swap del CPU sin bloquear (lock-free).

synchronized (Lock Pesado) Exclusión Mutua

Un hilo adquiere el cerrojo del monitor. Los demás pasan a estado BLOCKED esperando que se libere la llave.

Hilo 1 (Dentro) Hilo 2 (BLOCKED)
public synchronized void incrementar() {
    count++; // Protegido por Monitor
}
AtomicInteger (CAS Lock-Free) Hardware CPU

Compara el valor esperado con la memoria. Si coincide, intercambia en 1 ciclo de hardware; si no, reintenta sin suspenderse.

CAS Loop 1 Ciclo CPU
private AtomicInteger count = new AtomicInteger();
// Sin locks: lock-free hardware atomic
count.incrementAndGet();
Bloqueo Mutuo

El Peligro del Deadlock y las Reglas de Prevención

Un Deadlock ocurre cuando dos o más hilos quedan congelados eternamente esperando recursos retenidos mutuamente. Se previene imponiendo un orden canónico de adquisición.

Hilo A Hilo B Lock 1 Lock 2 Retiene Espera Retiene Espera
Condición de Espera Circular: El Hilo A no liberará Lock 1 hasta obtener Lock 2; el Hilo B no liberará Lock 2 hasta obtener Lock 1. Congelamiento total de la aplicación.
La Revolución de Java 21

De Platform Threads a Pools y Virtual Threads (Project Loom)

1 hilo tradicional del SO pesa ~1 MB de stack y es costoso de crear. Los Virtual Threads de Java 21 son ultraligeros (~1 KB) y gestionados íntegramente por la JVM.

Hilos Tradicionales del SO
10.000 Hilos: ~10 GB RAM (OutOfMemoryError)

1 Hilo Java = 1 Hilo pesado del kernel del SO. Bloquearse en I/O suspende el hilo nativo.

Virtual Threads (Project Loom)
10.000 Hilos: ~15 MB RAM (¡Ultraligero!)

Millones de hilos virtuales montados dinámicamente sobre pocos Carrier Threads. Si hay I/O, el virtual thread se desmonta sin frenar el CPU.

Sintaxis en Java 21+
// 1 millón de hilos concurrentes sin colapsar la máquina:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(1000); // Se desmonta de la CPU durante el sleep
            return i;
        });
    });
} // AutoCloseable espera a que todos terminen