M0490 · Programación de servicios y procesos · INS Marianao

Chuleta de procesos e hilos

La teoría del tema y la práctica del banco (FileLock y pipelines) en una sola página. Al final tienes un banco de preguntas con huecos y desplegables, como en el examen.

Teoría

8 temas

01Conceptos básicos

Programa

Serie de instrucciones guardada como archivo en disco. Está «quieto».

Proceso

Un programa en ejecución. Vive en la RAM y usa la CPU.

Hilo (thread)

Unidad mínima de ejecución dentro de un proceso. Comparte memoria con los otros hilos.

Servicio

Proceso en segundo plano, sin interacción con el usuario: servidor web, cola de impresión…

PCB (Process Control Block)

Cada proceso tiene un PCB con lo que el SO necesita para ejecutarlo: el código, los datos y el estado del procesador (registros y el PC, Program Counter).

Al ejecutar una app, el SO carga el ejecutable en memoria, le asigna recursos (CPU, memoria, ficheros…) y gestiona su ciclo de vida.

Proceso vs hilo

ProcesoHilo
MemoriaPropia, aisladaCompartida con su proceso
CostePesado, caro de crearLigero, rápido
SeguridadMás seguro: no corrompe a otrosSi un hilo falla, puede tumbar todo el proceso
ProblemaComunicarse cuesta más (IPC)Hay que sincronizar
Proceso: navegador memoria del proceso · la comparten todos sus hilos
hilo interfazhilo descargashilo música

Todo proceso tiene como mínimo 1 hilo. Con 1 hilo es monohilo; con varios, multihilo.

Ventajas de los hilos

Más eficientes que crear procesos, paralelismo en multinúcleo, compartir datos es fácil, buena respuesta en apps gráficas y en E/S.

Inconvenientes

Condiciones de carrera, interbloqueos, y un fallo afecta a todo el proceso.

02Estados de un proceso y planificación

Nuevo Preparado Ejecución Finalizado Bloqueado entra a la CPU / sale (quantum) espera E/S o evento llega el dato
  • Nuevo: creado, aún no se ejecuta.
  • Preparado (ready): listo, esperando la CPU.
  • Ejecución (running): usando la CPU.
  • Bloqueado (waiting): esperando un recurso o evento, como leer un fichero. Cuando llega, vuelve a Preparado, no a Ejecución.
  • Finalizado (terminated): ha acabado.

Planificadores

Hay más procesos que CPUs, así que el planificador decide quién se ejecuta y cuándo.

TipoQué hace
Largo plazoPasa programas del disco a memoria (los convierte en procesos).
Medio plazoSuspende y reanuda procesos; usa la zona de intercambio (swap) si el sistema está saturado.
Corto plazo (CPU)Elige qué proceso preparado entra a la CPU. El más importante para el rendimiento.

Algoritmos de planificación

AlgoritmoIdeaA favorEn contra
FCFS (First Come First Served)El primero que llega se ejecuta primeroSimpleUno largo hace esperar a todos: efecto convoy
SJN (Shortest Job Next)Primero el más cortoTiempo medio de respuesta bajoPerjudica a los largos
Round RobinCada uno tiene un tiempo fijo (quantum); si no acaba, va al final de la colaEquitativoQuantum corto: muchos cambios de contexto. Largo: se parece a FCFS
PrioridadesSiempre el de más prioridadAtiende lo urgenteInanición: los de baja prioridad esperan siempre

03Concurrencia, paralelismo y distribución

  • Concurrencia: varias tareas «a la vez». No siempre es paralelismo real.
  • 1 núcleo: se alternan muy rápido → multiprogramación.
  • Varios núcleos: paralelismo real → programación paralela.

Objetivos de la concurrencia: mejorar el tiempo de respuesta, aprovechar mejor los recursos (mientras uno espera al disco, otro usa la CPU) y modularidad (dividir la app en tareas independientes).

ParalelaDistribuida
DóndeVarios núcleos de un mismo ordenadorVarios ordenadores en red
MemoriaCompartida (misma RAM)Cada máquina la suya
ComunicaciónMemoria compartidaPor red
EjemploProcesar una imagen con 4 núcleosGoogle Search, torrent (P2P), juegos online

Problemas típicos

Condición de carrera

Dos hilos tocan la misma variable a la vez y el resultado depende del orden → incorrecto.

Interbloqueo (deadlock)

Cada uno espera un recurso que tiene bloqueado el otro → parados para siempre.

Inanición (starvation)

Uno de baja prioridad nunca llega a ejecutarse.

04Gestión de procesos e IPC

PID: identificador único del proceso. PPID: PID del proceso padre.

OrdenPara qué
ps / ps -efProcesos activos, con PID y PPID
top / htopLista en tiempo real (htop es más visual)
kill PIDEnvía SIGTERM: pide que pare
kill -9 PIDEnvía SIGKILL: lo mata al instante
jobs, fg, bgPrimer y segundo plano en la shell
jps -lProcesos Java con su PID y clase (lo usamos en la práctica)
Windows: Administrador de tareasVer y finalizar procesos
tasklistListar procesos (Windows)
taskkill /PID <pid>Finalizar un proceso (Windows)

IPC (Inter-Process Communication)

Cómo se pasan datos los procesos. Técnicas sencillas: ficheros y pipes. Más potentes: sockets, memoria compartida y colas de mensajes. Se elige según si es local o distribuida, si importa la velocidad o la persistencia, y si el acoplamiento es fuerte o débil.

Pipels | grep .java: la salida de un proceso es la entrada del otro.

Redirecciones de la shell

SímboloQué haceEn Java (ProcessBuilder)
< ficheroLa entrada estándar sale del ficheroredirectInput(file)
> ficheroLa salida va al fichero (lo sobrescribe)redirectOutput(file)
>> ficheroLa salida se añade al finalRedirect.appendTo(file)
2> ficheroLa salida de error va al ficheroredirectError(file)
a | bSalida de a → entrada de bstartPipeline(lista)

Sincronización entre procesos

  • Bloqueo de ficheros: solo un proceso puede escribir (nuestro FileLock).
  • Semáforos: contador que limita cuántos acceden. Binario = 1, como un candado; contador = N a la vez.
  • Colas de mensajes: además de comunicar, coordinan (uno espera el «permiso» del otro).
Ejemplo clásico de carreraCuenta con 100 €. A ingresa +50 y B retira −30. Los dos leen 100 a la vez → A escribe 150, B escribe 70 → quedan 70 € en lugar de 120 €.

05Procesos en Java

Runtime

Process p = Runtime.getRuntime().exec("notepad");   // lanza un proceso externo
p.waitFor();                                       // espera a que acabe

Métodos de Process

MétodoQué hace
getInputStream()Leer la salida estándar del proceso (lo que imprime)
getOutputStream()Escribir en su entrada estándar (como el teclado)
getErrorStream()Leer su salida de error
waitFor()Esperar a que termine (devuelve el código de salida)
exitValue()Código de salida (0 = bien)
pid() / isAlive()PID del proceso / si sigue vivo
Ojo con el nombregetInputStream() lee la salida del proceso hijo. Se llama «input» porque, para Java (el padre), es algo que entra y se lee.

ProcessBuilder (el recomendado)

Da más control de los argumentos, permite redirigir la entrada y salida, y es más claro.

ProcessBuilder pb = new ProcessBuilder("ls", "-l");   // orden y argumentos separados
pb.redirectOutput(new File("salida.txt"));          // también redirectInput / redirectError
pb.redirectError(ProcessBuilder.Redirect.INHERIT);   // errores del hijo en nuestra consola
pb.directory(new File("bin"));                        // carpeta de trabajo del hijo
Process p = pb.start();                               // ahora sí se ejecuta

Pipes en Java: ProcessBuilder.startPipeline(lista) arranca todos los procesos de la lista y conecta la salida de cada uno con la entrada del siguiente. Devuelve una List<Process>; el último tiene el resultado final.

06Hilos en Java

Todo programa Java empieza con el hilo principal (main thread). Hay dos formas de crear hilos:

// 1) Heredar de Thread
class MiHilo extends Thread {
    public void run() { System.out.println("Hola desde un hilo"); }
}
new MiHilo().start();

// 2) Implementar Runnable  (⭐ recomendada)
class MiTarea implements Runnable {
    public void run() { System.out.println("Hola"); }
}
new Thread(new MiTarea()).start();
start(), nunca run()Si llamas a run() directamente, se ejecuta en el mismo hilo, no en uno nuevo.

Por qué Runnable es mejor: Java no tiene herencia múltiple, así que tu clase puede heredar de otra a la vez, y el código se reutiliza mejor.

MétodoQué hace
sleep(ms)Pausa el hilo X milisegundos y libera la CPU (no suelta locks)
join()El que lo llama espera a que ese hilo termine
interrupt()Pide que pare, no lo mata. No usar stop(): deja recursos abiertos
setPriority(n)De 1 (MIN) a 10 (MAX). Por defecto 5 (NORM)

Thread.State: NEW (creado sin start), RUNNABLE, BLOCKED (esperando entrar en un synchronized), WAITING (wait/join sin tiempo), TIMED_WAITING (sleep, wait con tiempo), TERMINATED.

07Sincronización de hilos

  • Sección crítica: código que toca un recurso compartido. Solo un hilo a la vez.
  • Exclusión mutua (mutex): el mecanismo que lo garantiza.
  • count++ no es atómico (lee, suma, escribe) → con varios hilos da resultados erróneos.
  • Lectores/escritores: varios lectores a la vez, pero si escribe uno, nadie más accede.
// Método sincronizado: bloquea el objeto entero
public synchronized void incrementar() { count++; }

// Bloque sincronizado: solo bloquea la parte crítica (más eficiente)
public void incrementar() {
    synchronized (this) { count++; }
}
MonitorEn Java cualquier objeto tiene un lock. Si un hilo entra en synchronized(obj), los demás que quieran entrar en bloques sincronizados del mismo objeto esperan.

Deadlock y cómo evitarlo

Hilo 1 bloquea la cuenta A y quiere la B; hilo 2 bloquea la B y quiere la A → ninguno avanza. Solución: un orden global de bloqueo: todos bloquean siempre en el mismo orden, por ejemplo por id de cuenta.

08Comunicación entre hilos: wait() / notify()

No basta con no pisarse: a veces un hilo tiene que avisar a otro («ya hay datos»).

  • wait(): el hilo se duerme y suelta el lock hasta que le avisen.
  • notify(): despierta a un hilo que espera. notifyAll(): a todos.
  • Solo se pueden llamar dentro de un bloque synchronized (necesitan el monitor). Si no: IllegalMonitorStateException.
synchronized (obj) {
    while (!condicion) {     // ⚠️ while, NO if
        obj.wait();
    }
    // hacer el trabajo
    obj.notifyAll();
}

Se usa while y no if porque al despertar hay que volver a comprobar la condición: puede haber cambiado antes de recuperar el lock.

Productor / Consumidor

  • Los productores meten datos en un almacén compartido de capacidad limitada; los consumidores los sacan.
  • Almacén lleno → el productor hace wait().
  • Almacén vacío → el consumidor hace wait().
  • Cada vez que uno mete o saca algo, hace notify() para despertar al otro.

La práctica: FILS Bank Process

2 partes

P1Varios procesos, un extracto: FileLock

Cada entidad (titular, empresa…) ejecuta su propio BankProcess: si lo lanzas 3 veces, son 3 procesos con su memoria, su consola y su PID. Lo único que comparten es statement.txt, el recurso compartido. Escribir en él es la sección crítica.

ClaseQué haceConcepto de teoría
BankProcessMenú → concepto → importe → monitor.writeOperation(...) → ¿otra?Cada ejecución es un proceso (PID con ProcessHandle.current().pid())
AccountMonitorEscribe la línea en el fichero con FileLockExclusión mutua entre procesos (bloqueo de ficheros)
ProcessInputUtilsLee el teclado y pide confirmaciones—
public void writeOperation(Operation operation, String concept, double amount) {
    String linea = fecha + " | " + operation + " | " + concept + " | " + signo + importe + " €\n";

    try (FileOutputStream fos = new FileOutputStream(fileName, true);   // true = append
         FileLock lock = fos.getChannel().lock()) {                // 🔒 si otro lo tiene, ESPERA aquí
        fos.write(linea.getBytes(StandardCharsets.UTF_8));          // sección crítica
    } catch (IOException e) {
        System.err.println("Error: " + e.getMessage());
    }   // 🔓 el try-with-resources libera el lock y cierra el fichero (orden inverso)
}
PiezaPara qué
new FileOutputStream(f, true)Abre en modo append: añade al final sin borrar
getChannel()Da el canal, que sabe pedir bloqueos al SO
lock()Bloqueo exclusivo. Si lo tiene otro proceso, espera. (tryLock() no espera)
try-with-resourcesLibera el lock aunque haya excepción
FileLock vs synchronizedsynchronized protege entre hilos del mismo proceso (el lock está en la JVM). Aquí hay procesos distintos, cada uno con su JVM, así que hace falta FileLock, que bloquea el fichero a nivel de sistema operativo.
Proceso ADEPOSIT · NÒMINA · 1500terminado ✓
statement.txt🔓 libre
Proceso BWITHDRAW · COMPRES · 60terminado ✓

Con lock: B espera en lock() hasta que A termina. Las dos líneas quedan enteras.

La demostración (rúbrica)

  • Quitar la sincronización: escribiendo sin lock y letra a letra, el fichero sale mezclado (22002266--1100…) → condición de carrera.
  • Volver a poner el lock: el segundo proceso espera y las líneas salen enteras.
  • En el debugger: breakpoint en fos.write. Con lock, B no llega al breakpoint: sale como Running, atascado en lock(). Sin lock, los dos se paran a la vez.
# Lanzar varios procesos y gestionarlos (desde la carpeta del proyecto)
java -cp bin bank.BankProcess          # en 2 o 3 terminales
jps -l                                  # PID de cada proceso Java
ps -ef | grep BankProcess               # alternativa con ps (PID y PPID)
kill 4117                               # SIGTERM: pide que termine
kill -9 4117                            # SIGKILL: lo mata al instante

P212 tuberías en paralelo: ProcessBuilder

BankFilter hace desde Java lo mismo que esta línea de shell, pero para los 12 meses a la vez:

java StatementFilter amount -gt 100 < statement-01.txt | java StatementFilter amount -lt 200 >> filter.txt
statement-01.txt→filtro 1 · > 100→filtro 2 · < 200→filter.txt

Cada filtro es un proceso: 2 filtros × 12 meses = 24 procesos a la vez. StatementFilter lee de System.in y escribe en System.out, como un grep. Resultado: solo los SUPERMERCAT de 120 € (52 líneas).

public static void main(String[] args) throws IOException {
    deleteOldOutput();                                       // filter.txt se escribe en append
    List<Process> lastPipelineProcesses = startAllPipelines(); // fase 1: arrancar, SIN esperar
    collectOutputs(lastPipelineProcesses);                    // fase 2: recoger en orden
    System.exit(0);
}

private static Process startPipeline(String fileName) throws IOException {
    List<ProcessBuilder> builders = createProcessBuilders();          // un builder por filtro
    builders.get(0).redirectInput(new File("bank", fileName));      // solo el 1º lee el fichero  (= <)
    List<Process> processes = ProcessBuilder.startPipeline(builders); // arranca y conecta  (= |)
    return processes.get(processes.size() - 1);                       // el último tiene el resultado
}

private static void collectOutputs(List<Process> last) throws IOException {
    try (FileOutputStream fos = new FileOutputStream(OUTPUT_FILE, true)) {   // (= >>)
        for (Process process : last) {
            process.getInputStream().transferTo(fos);   // espera solo si ese mes no ha acabado
        }
    }
}
Por qué no hay waitFor() en el bucleSi esperaras dentro del bucle, febrero no arrancaría hasta que acabara enero: sería secuencial. Primero se arranca todo (paralelismo) y después se recoge.
El error de bin/binEl comando ya lleva -Duser.dir=bin. Si además pones pb.directory(new File("bin")), el hijo arranca en bin y busca en bin/bin → ClassNotFoundException. Con uno de los dos basta.

Lo que pide la rúbrica de la parte 2

# Ejecutar el filtro a mano con redirecciones (¡si no sabes, suspendes la parte 2!)
java -cp bin bank.filter.StatementFilter amount -gt 100 < bank/statement-01.txt > salida.txt
java -cp bin bank.filter.StatementFilter amount -gt 100 < bank/statement-01.txt | java -cp bin bank.filter.StatementFilter amount -lt 200 > enero.txt

# Un filtro sin < se queda esperando teclado: así puedes buscarlo y matarlo
java -cp bin bank.filter.StatementFilter concept -eq super
jps -l        # en otra terminal
kill 5230

# Modificar la pipeline: retiradas de 100 € o más
{ "java", "-Duser.dir=bin", FILTER_CLASS, "operation", "-eq", "withdraw" },
{ "java", "-Duser.dir=bin", FILTER_CLASS, "amount", "-ge", "100" }

Operadores de StatementFilter: columnas operation y concept con -eq/-ne (en concept, -eq significa «contiene»); amount con -lt -le -eq -ge -gt -ne. El importe se compara sin signo.

!!Trampas típicas de examen

✗ t.run()✓ t.start()

run() no crea hilo nuevo.

✗ getOutputStream() para leer lo que imprime✓ getInputStream()

Se nombra desde el punto de vista de Java.

✗ if (!cond) wait();✓ while (!cond) wait();

Al despertar hay que volver a comprobar.

✗ wait() fuera de synchronized✓ dentro de synchronized(obj)

Si no: IllegalMonitorStateException.

✗ wait() y sleep() son iguales✓ wait suelta el lock, sleep no

sleep solo pausa y libera la CPU.

✗ synchronized entre procesos✓ FileLock entre procesos

synchronized solo vale dentro de una JVM.

✗ Bloqueado → Ejecución✓ Bloqueado → Preparado

Vuelve a la cola y espera la CPU.

✗ kill = mata al instante✓ kill = SIGTERM · kill -9 = SIGKILL

kill solo pide que pare.

✗ waitFor() dentro del bucle de meses✓ arrancar todo y luego recoger

Si no, deja de ser paralelo.

✗ redirectInput en todos los filtros✓ solo en el primero

Los demás leen la salida del anterior.

✗ new FileOutputStream(f)✓ new FileOutputStream(f, true)

Sin true, cada escritura borra lo anterior.

✗ count++ es seguro con hilos✓ no es atómico

Lee, suma y escribe: otro hilo se mete en medio.

✗ interrupt() mata el hilo✓ interrupt() pide que pare

Y stop() no se usa: deja recursos abiertos.

✗ setPriority(0) / por defecto 1✓ de 1 a 10, por defecto 5

MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY.

✗ new ProcessBuilder("ls -l")✓ new ProcessBuilder("ls", "-l")

Orden y argumentos separados.

Preguntas tipo examen

rellena los desplegables
Comprobadas 0 de 0 preguntas · 0 / 0 huecos bien