Saltar al contenido principal

Java 5 · Concurrencia y procesos

Java 5 continúa lo aprendido en Java 4, donde preparaste proyectos Maven reproducibles, con tests, gestión de excepciones, debugging, logging y documentación. Este curso es una continuación avanzada y opcional: no forma parte de la ruta principal Backend y exige razonar sobre ejecuciones cuyo orden no siempre se puede predecir.

Hasta ahora, la mayoría de nuestros programas seguían un recorrido fácil de observar: una operación comenzaba después de la anterior. La concurrencia introduce la posibilidad de que varias tareas progresen durante el mismo periodo y de que sus pasos se intercalen. Si además se ejecutan realmente al mismo tiempo, hablamos de paralelismo. Un programa puede ser concurrente sin ejecutarse en paralelo.

Ese cambio abre nuevas posibilidades, pero también nuevos problemas. Cuando varias tareas comparten estado, pueden aparecer race conditions, problemas de visibilidad y operaciones que no son atómicas. Que un programa «funcione una vez» no demuestra que sea correcto: otra ejecución puede producir una intercalación diferente.

Por eso trabajaremos con experimentos observables y repetibles. En cada caso seguiremos este patrón:

predicción → ejecución repetida → evidencia → explicación → práctica

Antes de explicar un resultado, distinguiremos entre lo que Java garantiza, lo que puede ocurrir y lo que hemos observado en una ejecución concreta. No asumiremos un orden entre hilos si Java no lo garantiza.

Ideas que conviene fijar desde el principio​

  • sleep() introduce una espera, pero no es un mecanismo de sincronización.
  • volatile se relaciona principalmente con la visibilidad y con determinadas garantías de orden de memoria; no convierte operaciones compuestas como contador++ en atómicas.
  • Una solución concurrente correcta no implica necesariamente que haya paralelismo.
  • Añadir más hilos no garantiza más rendimiento y puede introducir coste y complejidad.

Durante el curso distinguiremos procesos e hilos, crearemos tareas con Thread y Runnable, y aprenderemos a gestionarlas con ExecutorService. Usaremos Future para obtener resultados y estudiaremos cómo cerrar correctamente los executors.

Después analizaremos el estado compartido. Separaremos tres conceptos que no son equivalentes: visibilidad, atomicidad y exclusión mutua. Veremos cuándo utilizar synchronized, cuáles son los límites de volatile y cómo identificar una posible condición de carrera sin afirmar que fallará siempre.

También coordinaremos tareas mediante interrupción y cancelación, observando tanto el camino normal como los errores y el cierre de recursos. Finalmente ejecutaremos procesos externos con ProcessBuilder, gestionaremos su salida estándar y de error, e interpretaremos sus códigos de salida.

Al terminar podrás construir un procesamiento concurrente acotado y observable: con tareas bien definidas, estado compartido protegido cuando sea necesario, cancelación controlada, recursos cerrados y evidencia suficiente para explicar su comportamiento.

Empezaremos diferenciando procesos, hilos y espacios de memoria, el modelo mínimo necesario para razonar con precisión sobre todo lo que sigue.