Serie de instrucciones escrita en un lenguaje de programación y compilada o interpretada para convertirla en ejecutable. Está guardada como archivo en el disco.
Chuleta de procesos
La teoría de procesos (apartados 1.1 a 1.9 de los apuntes), las clases de Runtime y ProcessBuilder 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
apuntes 1.1 – 1.91.1Introducción a los procesos
La ejecución de un programa, en el procesador o un núcleo de la CPU. Vive en la RAM y su información está en el PCB.
Unidad mínima de ejecución dentro de un proceso. Comparte el código y la memoria con los otros hilos del mismo proceso.
Proceso en segundo plano (background), sin interacción con el usuario. Ofrece funciones al sistema o a otras apps: servidor web, gestor de impresión, notificaciones.
PCB (Process Control Block)
Cada proceso tiene un PCB con lo que el SO necesita para ejecutarlo:
- El código del programa que se ejecuta.
- Los datos que utiliza.
- El estado del procesador: los valores de los registros, incluido el PC (Program Counter).
Ejecución de procesos
Al ejecutar una app (navegador, reproductor de música…), el SO crea un proceso y se encarga de:
- Cargar el ejecutable en memoria.
- Asignarle recursos (CPU, memoria, ficheros…).
- Gestionar su ciclo de vida.
Herramientas para ver los procesos
| Sistema | Herramienta |
|---|---|
| Linux | ps -aux, top y htop, y el gestor gráfico de procesos de cada distribución |
| Windows | Administrador de tareas (Task Manager) |
1.2Estados de un proceso y planificación
- 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
En los sistemas suele haber muchos más procesos que CPUs. Como necesitan la CPU para ejecutarse, el SO necesita un planificador que decida qué proceso se ejecuta y cuándo.
| Tipo | Qué hace |
|---|---|
| Largo plazo | Pasa programas del disco a memoria (los convierte en procesos) y les asigna una prioridad y un quantum. Controla el grado de multiprogramación: cuántos procesos hay en memoria. |
| Medio plazo | Suspende y reanuda procesos: si el sistema está saturado, decide cuáles se sacan de la memoria principal al espacio de intercambio (swap) y cuáles vuelven. |
| Corto plazo (CPU) | Elige qué proceso preparado entra a la CPU. El más importante para el rendimiento. |
Es el esquema de los apuntes con las dos flechas que faltaban en el original (modelo de 7 estados): de Bloqueado a Preparado, en memoria y fuera de memoria. Un bloqueado nunca pasa directamente a ejecución. El planificador a medio plazo puede sacar a un proceso preparado o bloqueado al swap («fuera de memoria») y luego devolverlo. Si un bloqueado fuera de memoria recibe su evento, pasa a preparado fuera de memoria.
Algoritmos de planificación
| Algoritmo | Idea | A favor | En contra |
|---|---|---|---|
| FCFS (First Come First Served) | El primero que llega se ejecuta primero | Simple | Uno largo hace esperar a todos: efecto convoy |
| SJN (Shortest Job Next) | Primero el más corto | Tiempo medio de respuesta bajo | Perjudica a los largos |
| Round Robin | Cada uno tiene un tiempo fijo (quantum); si no acaba, va al final de la cola | Equitativo | Quantum corto: muchos cambios de contexto. Largo: se parece a FCFS |
| Prioridades | Siempre el de más prioridad | Atiende lo urgente | Inanición: los de baja prioridad esperan siempre |
Al cambiar de proceso, el SO guarda el PCB (registros, PC) del que sale y carga el PCB del que entra. Es tiempo perdido: por eso un quantum muy corto es malo.
Round Robin es expulsivo: le quita la CPU al acabar el quantum. FCFS y SJN (básico) son no expulsivos: el proceso no suelta la CPU hasta que termina.
Para usarlo, el SO tiene que saber de antemano cuánto va a durar cada proceso. Es el que da el menor tiempo de espera medio.
Solución a la inanición: ir subiendo la prioridad de los procesos que llevan mucho rato esperando.
1.2bEl planificador paso a paso para practicar
En el examen te pueden dar una tabla de procesos y pedirte el orden de ejecución (diagrama de Gantt) y los tiempos. Estas son las dos fórmulas:
La ráfaga es lo que necesita de CPU. En los no expulsivos, la espera también es inicio − llegada.
| Proceso | Llegada | Ráfaga |
|---|---|---|
| P1 | 0 | 5 |
| P2 | 1 | 3 |
| P3 | 3 | 1 |
| P4 | 5 | 2 |
Cola de RR: el que agota el quantum va al final, detrás de los que han llegado mientras tanto. t=2: cola [P2, P1] · t=4: [P1, P3, P2] · t=6: [P3, P2, P4, P1].
| Espera | P1 | P2 | P3 | P4 | Media |
|---|---|---|---|---|---|
| FCFS | 0 | 4 | 5 | 4 | 3,25 |
| SJN | 0 | 7 | 2 | 1 | 2,5 (la mejor) |
| RR (q=2) | 6 | 4 | 3 | 3 | 4 |
1.3Introducción a los hilos
Un hilo de ejecución (thread) es la unidad básica de ejecución dentro de un proceso.
- Cada proceso tiene como mínimo un hilo.
- Los hilos del mismo proceso comparten memoria y recursos (ficheros, conexiones de red…).
- Por eso son mucho más ligeros que crear procesos nuevos.
| Proceso | Hilo | |
|---|---|---|
| Memoria | Propia, aislada | Compartida con su proceso |
| Coste | Pesado, caro de crear | Ligero, rápido |
| Seguridad | Más seguro: no corrompe a otros | Si un hilo falla, puede tumbar todo el proceso |
| Problema | Comunicarse cuesta más (IPC) | Hay que sincronizar |
Ejemplo: un navegador web (proceso) puede tener un hilo para la interfaz gráfica, otro que descarga ficheros y otro que reproduce la música de una pestaña. Todo pasa a la vez dentro del mismo proceso.
Mejor eficiencia (no se crean procesos nuevos, que son más pesados), posible paralelismo en máquinas con varios núcleos y compartir datos es más fácil porque todos ven la misma memoria.
Si no se controlan bien, aparecen condiciones de carrera e interbloqueos. Si un hilo falla, puede afectar a todo el proceso.
De hilos solo entra esta introducción: la programación con hilos en Java es del apartado «fils».
1.4Programación concurrente
Hay concurrencia cuando en un dispositivo se ejecutan de forma simultánea diferentes tareas (procesos). No siempre significa paralelismo real.
La concurrencia se consigue alternando muy rápido la ejecución de procesos o hilos. Se llama multiprogramación.
Puede haber paralelismo real (varias tareas exactamente a la vez), pero también alternancia, porque casi siempre hay más procesos que CPUs. Se llama programación paralela.
Objetivos de la concurrencia
- Mejorar el tiempo de respuesta: un navegador puede cargar imágenes mientras sigue mostrando texto.
- Aprovechar mejor los recursos: mientras un proceso espera datos del disco, otro usa la CPU.
- Facilitar la modularidad: es más natural dividir una aplicación en tareas independientes.
Concurrencia con procesos y con hilos
Cada proceso es independiente, con su memoria. Más seguro (uno no puede corromper la memoria de otro), pero más lento.
Comparten memoria: más rápidos, pero necesitan mecanismos de sincronización.
Problemas típicos de la concurrencia
Dos tareas acceden a la vez a un recurso compartido (una variable, un fichero) y el resultado es incorrecto.
Dos procesos esperan recursos que el otro tiene bloqueados y se quedan parados.
Un proceso de baja prioridad no llega nunca a ejecutarse porque los demás siempre se le adelantan.
1.5Programación paralela y distribuida
Definición: la aplicación se divide en tareas que se ejecutan a la vez en varios núcleos de un mismo ordenador.
Objetivo: reducir el tiempo total repartiendo el trabajo.
Clave: todas las tareas comparten la misma RAM.
Definición: la aplicación se ejecuta en varios ordenadores conectados en red.
Objetivo: repartir el trabajo entre máquinas, que pueden estar lejos.
Clave: cada ordenador tiene su memoria y hace falta comunicarse por red.
Ejemplos de aplicaciones distribuidas: Google Search (miles de servidores procesan búsquedas y combinan los resultados), P2P como torrent (los ordenadores comparten fragmentos de un fichero) y videojuegos multijugador (cada jugador ejecuta parte de la simulación y los servidores coordinan).
| Aspecto | Paralelo | Distribuido |
|---|---|---|
| Localización | Un mismo ordenador (multi-CPU / multi-core) | Varios ordenadores conectados en red |
| Memoria | Compartida (RAM común) | Cada máquina tiene la suya |
| Comunicación | Ficheros, pipes o variables compartidas (también sockets) | La red: sockets, mensajes, RPC |
| Ventajas | Baja latencia de acceso a los datos; más eficiencia con recursos locales | Escalabilidad y tolerancia a fallos; usar recursos repartidos geográficamente |
| Inconvenientes | Sincronizar los recursos compartidos | Coste de la comunicación en red; sincronización compleja |
1.6Gestión de procesos y monitorización
Una vez creados, el SO y los administradores o desarrolladores tienen que gestionar los procesos. Esto incluye:
- Consultar qué procesos hay en ejecución.
- Obtener información: PID, consumo de CPU, memoria, estado…
- Finalizar o suspender procesos que no responden o que hay que detener.
- Depurarlos y comprobar que funcionan.
Cada proceso tiene un PID (Process ID) único que le asigna el SO. Con él se le pueden enviar señales, consultar su estado o finalizarlo. El PPID (Parent Process ID) es el PID del proceso padre que lo creó. En Linux, ps -ef muestra las dos columnas; en Windows, el Administrador de tareas muestra el PID.
| Sistema | Orden | Para qué |
|---|---|---|
| Linux | ps / ps -ef / ps -aux | Procesos activos en ese momento (con -ef se ven PID y PPID) |
| Linux | top / htop | Monitorización en tiempo real de CPU y memoria (htop es más visual) |
| Linux | kill PID | Envía una señal; por defecto SIGTERM, que pide que pare |
| Linux | kill -9 PID | Envía SIGKILL: lo mata inmediatamente |
| Linux | jobs, fg, bg | Procesos en primer y segundo plano dentro de la shell |
| Linux | jps -l | Procesos Java con su PID y clase (lo usamos en la práctica) |
| Windows | Administrador de tareas | Ver procesos, consumo de recursos y finalizarlos |
| Windows | tasklist | Lista los procesos activos en consola |
| Windows | taskkill /PID <pid> | Finaliza un proceso concreto por PID |
1.7Comunicación entre procesos (IPC)
Cuando dos o más procesos se ejecutan, a menudo tienen que intercambiar información: enviar datos, recibir resultados o coordinarse. Esto es la IPC (Inter-Process Communication), y el SO ofrece varios mecanismos.
Ejemplos: un proceso lee un sensor y otro muestra los datos en pantalla; un navegador lanza otro proceso para los plugins y los dos se comunican; en ls | grep .java la salida de un proceso es la entrada del otro.
| Técnica | Cómo funciona | Ejemplo | Ventajas | Inconvenientes |
|---|---|---|---|---|
| Ficheros | Leen y escriben en un mismo fichero | Uno guarda un log y otro lo analiza | Sencillo y persistente | Lento (disco) y riesgo de bloqueos |
| Pipes | La salida de uno va a la entrada de otro | ls | grep ".java" | Muy útil en Linux; sin ficheros temporales | Solo secuencial y no permanente |
| Sockets | Comunicación por red, local o remota | Cliente-servidor con localhost | Flexibles: local y distribuido | Más complejos: puertos y protocolos |
| Memoria compartida | Varios procesos acceden a la misma zona de memoria | Segmento de memoria compartido | Muy rápida | Necesita sincronización (condiciones de carrera) |
| Mensajes (colas) | Se envían mensajes estructurados | Cola de mensajes del SO | Asíncrono, desacopla procesos, robusto | Más lento que la memoria compartida; necesita un sistema de colas |
Hay técnicas sencillas (ficheros, pipes) y otras más potentes (sockets, memoria compartida, mensajes). Se escoge según si la comunicación es local o distribuida, si hace falta velocidad o persistencia, y si los procesos están fuerte o débilmente acoplados.
Redirecciones de la shell
| Símbolo | Qué hace | En Java (ProcessBuilder) |
|---|---|---|
< fichero | La entrada estándar sale del fichero | redirectInput(file) |
> fichero | La salida va al fichero (lo sobrescribe) | redirectOutput(file) |
>> fichero | La salida se añade al final | Redirect.appendTo(file) |
2> fichero | La salida de error va al fichero | redirectError(file) |
a | b | Salida de a → entrada de b | startPipeline(lista) o a mano (ver Pipes.java) |
1.8Sincronización entre procesos
Cuando dos o más procesos acceden al mismo recurso sin coordinarse, puede haber condiciones de carrera. Por ejemplo, dos procesos escriben en el mismo fichero a la vez y el resultado es inconsistente.
A escribe «Hola» y B escribe «Adéu» en el mismo fichero a la vez. Sin sincronización puede salir:HolaAdéu (correcto) · HoAdéula (texto corrupto) · AdéuHola (orden cambiado).
La cuenta tiene 100 €. A ingresa +50 y B retira −30. A lee 100, B lee 100, A escribe 150, B escribe 70. Resultado: 70 €, se ha perdido el ingreso. Lo correcto: 120 €.
Necesidad de sincronización
El SO ofrece mecanismos para coordinar el acceso a los recursos compartidos. Aseguran que:
- Solo un proceso pueda modificar el recurso en un momento dado.
- Los procesos esperen su turno antes de continuar.
- El resultado final sea correcto y determinado.
Mecanismos de sincronización entre procesos
El SO deja bloquear un fichero para que solo un proceso tenga permiso de escritura. Evita que se mezclen los datos. Ejemplo: un procesador de textos bloquea el documento mientras se edita. En la práctica: FileLock.
Un contador que controla cuántos procesos acceden a la vez. Binario: uno solo (como un candado). Contador: N en paralelo. Los gestiona el SO y se comparten entre procesos.
Además de comunicar, coordinan: un proceso envía un mensaje de permiso y el otro no empieza hasta recibirlo. Más flexible que un simple bloqueo.
| Mecanismo | Funcionamiento | Ejemplo de uso |
|---|---|---|
| Bloqueo de ficheros | Impide que más de un proceso escriba en el fichero a la vez | Editor de texto que bloquea el documento en uso |
| Semáforos | Controlan el acceso a un recurso compartido con un contador | Control de acceso a una impresora |
| Colas de mensajes | Envían señales de permiso o de finalización | Un proceso no empieza hasta recibir el «ok» de otro |
1.9Programación de aplicaciones multiproceso
Cómo crea y gestiona procesos Java, y cómo se comunican: la parte práctica de todo lo anterior.
La clase Runtime · LaunchProcess.java
package runtime; public class LaunchProcess { // private static final String PROGRAM = "notepad"; // Windows // private static final String PROGRAM = "gedit"; // Linux private static final String PROGRAM = "pluma"; // Linux public static void main(String[] args) { try { Process process = Runtime.getRuntime().exec(PROGRAM); // lanza el editor process.waitFor(); // espera a que lo cierres System.out.println(PROGRAM + " has been closed."); } catch (IOException e) { e.printStackTrace(); System.exit(1); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); System.exit(2); } System.exit(0); } }
Runtime.getRuntime().exec(...) lanza un proceso externo y process.waitFor() hace que el programa Java espere a que acabe.
La clase Process
| Método | Qué hace |
|---|---|
exitValue() | Código de salida (0 = bien). Si aún no ha acabado: IllegalThreadStateException |
getInputStream() | Stream de entrada conectado con la salida estándar del proceso: leemos lo que escribe por consola |
getOutputStream() | Stream de salida conectado con la entrada estándar del proceso: le escribimos como si fuera el teclado |
getErrorStream() | Stream de entrada conectado con la salida de error: leemos sus errores |
waitFor() | Espera a que acabe y devuelve el código de salida |
pid() / isAlive() | PID del proceso / si sigue vivo |
destroy() / destroyForcibly() | Pide que termine (como kill) / lo mata (como kill -9) |
ProcessHandle.current().pid() | PID del proceso actual (lo usamos en BankProcess) |
getInputStream() lee la salida del proceso hijo. Se llama «input» porque, para Java (el padre), es algo que entra y se lee.ListFiles.java con Runtime (package process)
private static final String COMMAND = "ls"; // Windows: "dir" ... Process process = Runtime.getRuntime().exec(COMMAND); System.out.printf("Started process %d: %s%n", process.pid(), String.join(" ", COMMAND)); process.getInputStream().transferTo(System.out); // lo que imprime ls → a nuestra consola System.out.printf("Process %d exited with code: %d%n", process.pid(), process.waitFor());
La clase ProcessBuilder (la recomendada)
Java recomienda ProcessBuilder frente a Runtime.exec() porque da más control sobre los argumentos, permite redirigir la entrada y la salida a ficheros o a otros flujos, y su configuración es más clara y flexible.
ListFiles.java con ProcessBuilder (package procesbuilder)
// private static final String[] COMMAND = {"dir", "/n"}; // Windows private static final String[] COMMAND = { "ls", "-l" }; // Linux: orden y argumentos separados ... ProcessBuilder processBuilder = new ProcessBuilder(COMMAND); Process process = processBuilder.start(); // ejecuta el proceso System.out.printf("Started process %d: %s%n", process.pid(), String.join(" ", processBuilder.command())); process.getInputStream().transferTo(System.out); // la salida se lee igual que con Runtime System.out.printf("Process %d exited with code: %d%n", process.pid(), process.waitFor());
RedirectOutput.java
processBuilder.redirectOutput(new File(OUTPUT_FILE)); // la salida va directamente a output.txt // también existen redirectInput(File) y redirectError(File)
Pipes.java · conectar dos procesos a mano
private static final String[] COMMAND_1 = { "ls" }; // Windows: {"dir"} private static final String[] COMMAND_2 = { "grep", "txt" }; // Windows: {"findstr", "txt"} ... Process process1 = new ProcessBuilder(COMMAND_1).start(); Process process2 = new ProcessBuilder(COMMAND_2).start(); // Conectar la salida de process1 con la entrada de process2 InputStream inputStream = process1.getInputStream(); // lo que imprime ls (el padre LEE) OutputStream outputStream = process2.getOutputStream(); // la entrada de grep (el padre ESCRIBE) inputStream.transferTo(outputStream); // pasa todo de ls a grep outputStream.close(); // ⚠️ importante: así grep sabe que no hay más datos process2.getInputStream().transferTo(System.out); // resultado final de grep System.out.printf("Process %d exited with code: %d%n", process1.pid(), process1.waitFor()); System.out.printf("Process %d exited with code: %d%n", process2.pid(), process2.waitFor());
process2.getInputStream().transferTo(...).Pipeline.java · lo mismo, automático
private static final String[][] PIPE_LINE = { COMMAND_1, COMMAND_2 }; ... List<ProcessBuilder> processBuilders = new ArrayList<>(); for (String[] command : PIPE_LINE) { processBuilders.add(new ProcessBuilder(command)); } List<Process> processes = ProcessBuilder.startPipeline(processBuilders); // arranca y conecta ls → grep for (int i = 0; i < PIPE_LINE.length; i++) { System.out.printf("Started process %d: %s%n", processes.get(i).pid(), String.join(" ", processBuilders.get(i).command())); } Process last = processes.get(processes.size() - 1); // el último tiene el resultado last.getInputStream().transferTo(System.out); for (Process p : processes) { System.out.printf("Process %d exited with code: %d%n", p.pid(), p.waitFor()); }
En Pipes el padre hace de puente: lee de uno y escribe en el otro, y tiene que cerrar. En Pipeline lo conecta Java con startPipeline y el padre solo lee el último.
Solo el primero puede tener redirectInput y solo el último redirectOutput. Si no, IllegalArgumentException.
No funciona: buscaría un programa llamado «ls -l». Cada palabra va en un argumento: "ls", "-l".
1.9bEntrada y salida: quién escribe y quién lee
La regla para no liarte: mírala siempre desde el padre (tu programa Java). Si el padre lee algo del hijo, es un InputStream. Si el padre escribe algo al hijo, es un OutputStream.
p.getOutputStream()el padre escribe → le llega al hijo por System.inp.getInputStream()lo que el hijo imprime con System.out → lo lee el padrep.getErrorStream()los errores del hijo (System.err)| Quiero… | Uso | En la shell |
|---|---|---|
| Coger lo que imprime el hijo | p.getInputStream() | — |
| Pasarle datos al hijo | p.getOutputStream() (y cerrarlo al acabar) | — |
| Leer los errores del hijo | p.getErrorStream() | — |
| Que el hijo lea un fichero | pb.redirectInput(new File("f")) | < f |
| Que el hijo escriba en un fichero (borrando) | pb.redirectOutput(new File("f")) | > f |
| Que el hijo añada al final del fichero | pb.redirectOutput(Redirect.appendTo(new File("f"))) | >> f |
| Errores del hijo a un fichero | pb.redirectError(new File("f")) | 2> f |
| Juntar errores con la salida normal | pb.redirectErrorStream(true) | 2>&1 |
| Errores del hijo en mi consola | pb.redirectError(Redirect.INHERIT) | — |
| Todo (entrada, salida y errores) en mi consola | pb.inheritIO() | — |
| La salida de A es la entrada de B | ProcessBuilder.startPipeline(List.of(a, b)) | a | b |
Es el valor por defecto: el stream del hijo queda conectado con el padre y se usa con get…Stream().
…p.getInputStream() devuelve un stream vacío: la salida ya no pasa por el padre.
Si le pasas datos a sort, hay que cerrar el OutputStream. Si no, sort sigue esperando datos y nunca contesta.
redirectInput = <). startPipeline conecta la salida del filtro 1 con la entrada del 2 (= |). El padre coge la salida del último con getInputStream() y la pasa a filter.txt con transferTo en modo append (= >>).1.9cPlantilla de los ejercicios y variantes
Las clases de los apuntes (LaunchProcess, ListFiles, RedirectOutput, Pipes y Pipeline) siguen todas la misma plantilla. Si la entiendes, en cada ejercicio solo cambia la línea del medio. Las marcadas como extra no salen en los apuntes: son variantes con los mismos métodos para practicar.
public class RedirectOutput { private static final String OUTPUT_FILE = "output.txt"; // private static final String[] COMMAND = {"cmd", "/c", "dir"}; // Windows private static final String[] COMMAND = { "ls", "-l" }; // Linux: cada argumento por separado public static void main(String[] args) { try { ProcessBuilder processBuilder = new ProcessBuilder(COMMAND); // 1. la receta processBuilder.redirectOutput(new File(OUTPUT_FILE)); // 2. ⭐ lo que cambia en cada ejercicio Process process = processBuilder.start(); // 3. arrancar (IOException) System.out.printf("Started process %d: %s%n", process.pid(), String.join(" ", processBuilder.command())); // PID y comando lanzado System.out.printf("Process %d exited with code: %d%n", process.pid(), process.waitFor()); // 4. esperar (InterruptedException) } catch (IOException e) { // no se pudo lanzar: comando que no existe, fichero que no se abre… e.printStackTrace(); System.exit(1); } catch (InterruptedException e) { // nos interrumpieron mientras esperábamos e.printStackTrace(); Thread.currentThread().interrupt(); System.exit(2); } System.exit(0); // todo bien } }
| Ejercicio | La línea que cambia | En la shell |
|---|---|---|
| LaunchProcess | Runtime.getRuntime().exec(PROGRAM) + waitFor() | pluma |
| ListFiles (Runtime / ProcessBuilder) | p.getInputStream().transferTo(System.out) | ls -l |
| RedirectOutput | redirectOutput(new File("output.txt")) | ls -l > output.txt |
| AppendOutput extra | redirectOutput(Redirect.appendTo(new File("log.txt"))) | date >> log.txt |
| RedirectInput extra | redirectInput(new File("nombres.txt")) | sort < nombres.txt |
| RedirectError extra | redirectError(new File("error.txt")) | ls /no-existe 2> error.txt |
| MergeErrors extra | redirectErrorStream(true) | 2>&1 |
| InheritIO extra | inheritIO() | el hijo usa tu consola |
| ReadOutput extra | new BufferedReader(new InputStreamReader(p.getInputStream())) | el padre lee |
| WriteInput extra | new BufferedWriter(new OutputStreamWriter(p.getOutputStream())) + close() | el padre escribe |
| Pipes | p1.getInputStream().transferTo(p2.getOutputStream()) + close() | ls | grep txt |
| Pipeline | ProcessBuilder.startPipeline(processBuilders) | ls | grep txt |
| DestroyProcess extra | isAlive(), destroy(), destroyForcibly() | kill / kill -9 |
| WorkingDirectory extra | directory(new File("/tmp")) | cd /tmp && ls |
| Environment extra | environment().put("NOMBRE", "Ana") | NOMBRE=Ana cmd |
Los del programa Java: 0 bien, 1 IOException, 2 InterruptedException. El del hijo lo da waitFor(): 0 bien, otro número = error. Si lo matan con una señal N: 128 + N (143 con SIGTERM).
IllegalThreadStateException (exitValue antes de tiempo) e IllegalArgumentException (startPipeline mal montada) no son IOException: el programa se cae con la traza.
…sin redirectOutput, sin inheritIO y sin leer getInputStream(), lo que imprime el hijo se queda en el PIPE y no aparece en ningún sitio.
La práctica: FILS Bank Process
2 partesP1Varios 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.
| Clase | Qué hace | Concepto de teoría |
|---|---|---|
| BankProcess | Menú → concepto → importe → monitor.writeOperation(...) → ¿otra? | Cada ejecución es un proceso (PID con ProcessHandle.current().pid()) |
| AccountMonitor | Escribe la línea en el fichero con FileLock | Exclusión mutua entre procesos (bloqueo de ficheros) |
| ProcessInputUtils | Lee 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) }
| Pieza | Para 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-resources | Libera el lock aunque haya excepción |
synchronized 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.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 enlock(). 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
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 } } }
-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
Se nombra desde el punto de vista de Java.
synchronized solo vale dentro de una JVM.
Vuelve a la cola y espera la CPU.
kill solo pide que pare.
Si no, deja de ser paralelo.
Los demás leen la salida del anterior.
Sin true, cada escritura borra lo anterior.
Orden y argumentos separados.
Si no ha acabado: IllegalThreadStateException.
Con > vacías el extracto y el filtro espera teclado.
startPipeline lanza IllegalArgumentException.
Si no, sort espera más datos para siempre.
Aunque le des CPU a uno bloqueado, no puede avanzar.
Nadie llega a agotar el quantum.