G3-Critical-Sections-Without-Bottlenecks-Lab-Java

G3-Critical-Sections-Without-Bottlenecks-Lab

Java Critical Sections Without Bottlenecks

Laboratorio práctico para aprender a proteger recursos compartidos sin convertir toda la aplicación en una fila de espera.

El proyecto compara diferentes estrategias de sincronización en Java 25 LTS, mostrando cómo el alcance de un bloqueo afecta el rendimiento, la concurrencia y la escalabilidad de una aplicación.

Idea clave: un buen lock protege únicamente el dato compartido; un mal lock secuestra toda la aplicación.


El problema

Cuando varios hilos consultan o modifican simultáneamente un mismo recurso, pueden producirse:

La solución habitual consiste en utilizar sincronización o exclusión mutua. Sin embargo, proteger demasiado código también puede provocar:

El verdadero reto no es simplemente agregar un lock, sino colocarlo exactamente donde se necesita.


Objetivo

Demostrar mediante código y mediciones cómo:

  1. Identificar el estado que realmente es compartido.
  2. Delimitar correctamente una sección crítica.
  3. Reducir el tiempo durante el cual se mantiene un bloqueo.
  4. Ejecutar validaciones y operaciones independientes en paralelo.
  5. Comparar distintas estrategias de sincronización.
  6. Medir contención, latencia y rendimiento.

Caso de estudio

El proyecto simula un sistema de reservas de inventario.

Varios hilos intentan reservar unidades de un producto al mismo tiempo. El sistema debe garantizar que:


Estrategias implementadas

Estrategia Descripción Propósito
Bloqueo amplio Protege todo el método Mostrar el impacto de bloquear demasiado código
Bloqueo reducido Protege solamente la actualización del inventario Reducir la contención
ReentrantLock Permite control explícito del bloqueo Garantizar liberación y flexibilidad
tryLock() Evita esperas indefinidas Aplicar timeout y recuperación controlada
Operaciones atómicas Actualización mediante compare-and-set Evitar locks en operaciones simples
Read/write lock Separa lectores de escritores Mejorar escenarios con muchas consultas
Sin sincronización Implementación deliberadamente insegura Evidenciar condiciones de carrera

Ejemplo de mala implementación

En este ejemplo, todo el método está sincronizado:

public synchronized ReservationResult reserve(
        String productId,
        int quantity
) {
    validateRequest(productId, quantity);

    simulateExternalValidation();
    simulateDatabaseQuery();

    if (stock < quantity) {
        return ReservationResult.rejected(productId, quantity);
    }

    stock -= quantity;

    simulateAuditRegistration();
    simulateNotification();

    return ReservationResult.approved(productId, quantity, stock);
}

Aunque solamente la lectura y modificación de stock necesitan exclusión mutua, las siguientes operaciones también quedan bloqueadas:

Esto obliga a que los hilos ejecuten en serie operaciones que podrían realizarse en paralelo.


Implementación optimizada

package com.workorderit.criticalsection.service;

import java.util.concurrent.locks.ReentrantLock;

public final class OptimizedInventoryService {

    private final ReentrantLock inventoryLock = new ReentrantLock();

    private int availableStock;

    public OptimizedInventoryService(int initialStock) {
        if (initialStock < 0) {
            throw new IllegalArgumentException(
                    "Initial stock cannot be negative"
            );
        }

        this.availableStock = initialStock;
    }

    public ReservationResult reserve(
            String productId,
            int quantity
    ) {
        validateRequest(productId, quantity);

        // Trabajo independiente realizado fuera del lock.
        ReservationContext context = prepareReservation(
                productId,
                quantity
        );

        ReservationResult result;

        inventoryLock.lock();

        try {
            // Solo el acceso al estado compartido forma parte
            // de la sección crítica.
            if (availableStock < quantity) {
                result = ReservationResult.rejected(
                        productId,
                        quantity,
                        availableStock
                );
            } else {
                availableStock -= quantity;

                result = ReservationResult.approved(
                        productId,
                        quantity,
                        availableStock
                );
            }
        } finally {
            inventoryLock.unlock();
        }

        // Auditoría, métricas y notificaciones fuera del lock.
        completeReservation(context, result);

        return result;
    }

    public int getAvailableStock() {
        inventoryLock.lock();

        try {
            return availableStock;
        } finally {
            inventoryLock.unlock();
        }
    }

    private void validateRequest(
            String productId,
            int quantity
    ) {
        if (productId == null || productId.isBlank()) {
            throw new IllegalArgumentException(
                    "Product ID is required"
            );
        }

        if (quantity <= 0) {
            throw new IllegalArgumentException(
                    "Quantity must be greater than zero"
            );
        }
    }

    private ReservationContext prepareReservation(
            String productId,
            int quantity
    ) {
        return new ReservationContext(productId, quantity);
    }

    private void completeReservation(
            ReservationContext context,
            ReservationResult result
    ) {
        // Simulación de auditoría, métricas o notificaciones.
    }
}

La sección crítica queda reducida a:

inventoryLock.lock();

try {
    if (availableStock >= quantity) {
        availableStock -= quantity;
    }
} finally {
    inventoryLock.unlock();
}

Flujo de ejecución

Validar solicitud
       │
       ▼
Preparar operación
       │
       ▼
Solicitar el lock
       │
       ▼
┌─────────────────────────────┐
│      SECCIÓN CRÍTICA        │
│                             │
│  Consultar inventario       │
│  Actualizar inventario      │
│  Construir resultado        │
└─────────────────────────────┘
       │
       ▼
Liberar el lock
       │
       ▼
Auditoría, métricas y eventos

El lock se mantiene durante el menor tiempo posible.


Estructura propuesta

java-critical-sections-without-bottlenecks/
├── pom.xml
├── README.md
├── LICENSE
├── .gitignore
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── workorderit
    │               └── criticalsection
    │                   ├── CriticalSectionApplication.java
    │                   ├── model
    │                   │   ├── ReservationContext.java
    │                   │   └── ReservationResult.java
    │                   ├── service
    │                   │   ├── UnsafeInventoryService.java
    │                   │   ├── CoarseGrainedLockService.java
    │                   │   ├── OptimizedInventoryService.java
    │                   │   ├── TryLockInventoryService.java
    │                   │   ├── AtomicInventoryService.java
    │                   │   └── ReadWriteInventoryService.java
    │                   └── metrics
    │                       ├── ExecutionMetrics.java
    │                       └── PerformanceReport.java
    └── test
        └── java
            └── com
                └── workorderit
                    └── criticalsection
                        ├── InventoryConsistencyTest.java
                        └── ConcurrentReservationTest.java

Tecnologías


Requisitos

Verifica la instalación de Java:

java --version

El resultado debe indicar Java 25 LTS.

Verifica Maven:

mvn --version

También debes confirmar que Maven esté utilizando el JDK 25.


Compilación

mvn clean compile

Ejecución de pruebas

mvn clean test

Empaquetado

mvn clean package

Ejecución

java -cp target/classes \
  com.workorderit.criticalsection.CriticalSectionApplication

En Windows:

java -cp target/classes com.workorderit.criticalsection.CriticalSectionApplication

Métricas evaluadas

Cada escenario puede producir un reporte con:

Strategy: Optimized ReentrantLock
Requests: 100,000
Approved reservations: 10,000
Rejected reservations: 90,000
Final inventory: 0
Execution time: 428 ms
Average latency: 0.004 ms
Throughput: 233,644 operations/second
Consistency errors: 0

Las métricas principales son:

Métrica Significado
Tiempo total Duración completa de la simulación
Throughput Operaciones procesadas por segundo
Latencia promedio Tiempo medio por operación
Tiempo de espera Tiempo empleado esperando el lock
Contención Cantidad de hilos compitiendo por el recurso
Errores de consistencia Actualizaciones o resultados incorrectos
Inventario final Validación del estado compartido

Principios aplicados

1. Proteger el dato, no el método completo

La exclusión mutua debe abarcar únicamente las instrucciones que consultan o modifican el estado compartido.

2. No realizar operaciones lentas dentro del lock

Se deben mantener fuera de la sección crítica operaciones como:

3. Liberar siempre el lock

lock.lock();

try {
    updateSharedState();
} finally {
    lock.unlock();
}

El bloque finally garantiza la liberación aunque ocurra una excepción.

4. Evitar un lock global innecesario

Los recursos independientes deberían utilizar mecanismos de sincronización independientes.

Producto A ── Lock A
Producto B ── Lock B
Producto C ── Lock C

De esta manera, una reserva del producto A no bloquea las operaciones relacionadas con los productos B y C.

5. Medir antes de optimizar

Una solución concurrente no debe evaluarse solamente porque “parece más rápida”. Debe medirse bajo carga y comprobarse que conserva la consistencia.


Preguntas que responde el proyecto


Resultados esperados

Al completar el proyecto, el desarrollador podrá:


Regla principal

Sincroniza únicamente lo que debe ser indivisible.
Todo lo demás debe poder ejecutarse en paralelo.

Próximas mejoras


Advertencia sobre sistemas distribuidos

Los mecanismos synchronized y ReentrantLock protegen recursos dentro de una misma instancia de la JVM.

Cuando la aplicación se ejecuta en múltiples servidores o contenedores, puede ser necesario utilizar otras estrategias:


Autor

Work Order IT
Soluciones tecnológicas, arquitectura de software y formación técnica para equipos de desarrollo.

Este repositorio forma parte de una iniciativa educativa orientada a explicar cómo la concurrencia en Python 3.13 puede acelerar un sistema o volverlo impredecible cuando el estado compartido no se controla correctamente.

Website: www.workorder-it.net