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.
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.
Demostrar mediante código y mediciones cómo:
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:
| 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 |
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.
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();
}
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.
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
ExecutorService.synchronized.ReentrantLock.ReadWriteLock.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.
mvn clean compile
mvn clean test
mvn clean package
java -cp target/classes \
com.workorderit.criticalsection.CriticalSectionApplication
En Windows:
java -cp target/classes com.workorderit.criticalsection.CriticalSectionApplication
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 |
La exclusión mutua debe abarcar únicamente las instrucciones que consultan o modifican el estado compartido.
Se deben mantener fuera de la sección crítica operaciones como:
lock.lock();
try {
updateSharedState();
} finally {
lock.unlock();
}
El bloque finally garantiza la liberación aunque ocurra una excepción.
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.
Una solución concurrente no debe evaluarse solamente porque “parece más rápida”. Debe medirse bajo carga y comprobarse que conserva la consistencia.
synchronized?ReentrantLock?Al completar el proyecto, el desarrollador podrá:
Sincroniza únicamente lo que debe ser indivisible.
Todo lo demás debe poder ejecutarse en paralelo.
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:
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