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

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.9

1.1Introducción a los procesos

Programa

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.

Proceso

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.

Hilo (fil / thread)

Unidad mínima de ejecución dentro de un proceso. Comparte el código y la memoria con los otros hilos del mismo proceso.

Servicio

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).
Ejemplo del editor de textoSi abres el editor tres veces (currículum, trabajo de clase y una carta), el SO crea 3 procesos a partir del mismo programa del disco, y cada uno tiene su PCB. Dentro de cada proceso puede haber varios hilos: uno recoge lo que escribes, otro pasa el corrector y otro envía el documento a la impresora.

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

SistemaHerramienta
Linuxps -aux, top y htop, y el gestor gráfico de procesos de cada distribución
WindowsAdministrador de tareas (Task Manager)

1.2Estados 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

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.

TipoQué hace
Largo plazoPasa 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 plazoSuspende 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.
En ejecución Acabado Preparadoen memoria Bloqueadoen memoria Preparadofuera de memoria (swap) Bloqueadofuera de memoria (swap) largo plazo(entra un proceso nuevo) corto plazo medio plazo(sacar / volver a memoria)

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

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
Cambio de contexto

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.

Expulsivo vs no expulsivo

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.

SJN tiene trampa

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.

Envejecimientopara nota

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:

Tiempo de retorno = fin − llegadaTiempo de espera = retorno − ráfaga

La ráfaga es lo que necesita de CPU. En los no expulsivos, la espera también es inicio − llegada.

ProcesoLlegadaRáfaga
P105
P213
P331
P452
FCFS · por orden de llegada
0P15
P28
P39
P411
SJN · al acabar P1 (t=5) ya han llegado todos: primero el más corto
0P15
P36
P48
P211
Round Robin · quantum = 2
0P12
P24
P16
P37
P28
P410
P111

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].

EsperaP1P2P3P4Media
FCFS04543,25
SJN07212,5 (la mejor)
RR (q=2)64334
Cómo se calcula la espera de P1 en RRTermina en 11 y llegó en 0 → retorno = 11. Su ráfaga es 5 → espera = 11 − 5 = 6.

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.
Diferencia claveEn un proceso, cada instancia tiene su propio espacio de memoria → más aislados, pero más costosos. Entre hilos de un mismo proceso todo se comparte → más rápidos, pero hay que controlar la sincronización.
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

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.

Ventajas de los hilos

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.

Inconvenientes

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.

1 núcleo (monoprocesador)

La concurrencia se consigue alternando muy rápido la ejecución de procesos o hilos. Se llama multiprogramación.

Varios núcleos (multiprocesador)

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

Con procesos

Cada proceso es independiente, con su memoria. Más seguro (uno no puede corromper la memoria de otro), pero más lento.

Con hilos

Comparten memoria: más rápidos, pero necesitan mecanismos de sincronización.

Problemas típicos de la concurrencia

Condición de carrera

Dos tareas acceden a la vez a un recurso compartido (una variable, un fichero) y el resultado es incorrecto.

Interbloqueo (deadlock)

Dos procesos esperan recursos que el otro tiene bloqueados y se quedan parados.

Inanición (starvation)

Un proceso de baja prioridad no llega nunca a ejecutarse porque los demás siempre se le adelantan.

1.5Programación paralela y distribuida

Programación paralela

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.

Programación distribuida

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).

AspectoParaleloDistribuido
LocalizaciónUn mismo ordenador (multi-CPU / multi-core)Varios ordenadores conectados en red
MemoriaCompartida (RAM común)Cada máquina tiene la suya
ComunicaciónFicheros, pipes o variables compartidas (también sockets)La red: sockets, mensajes, RPC
VentajasBaja latencia de acceso a los datos; más eficiencia con recursos localesEscalabilidad y tolerancia a fallos; usar recursos repartidos geográficamente
InconvenientesSincronizar los recursos compartidosCoste 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.

SistemaOrdenPara qué
Linuxps / ps -ef / ps -auxProcesos activos en ese momento (con -ef se ven PID y PPID)
Linuxtop / htopMonitorización en tiempo real de CPU y memoria (htop es más visual)
Linuxkill PIDEnvía una señal; por defecto SIGTERM, que pide que pare
Linuxkill -9 PIDEnvía SIGKILL: lo mata inmediatamente
Linuxjobs, fg, bgProcesos en primer y segundo plano dentro de la shell
Linuxjps -lProcesos Java con su PID y clase (lo usamos en la práctica)
WindowsAdministrador de tareasVer procesos, consumo de recursos y finalizarlos
WindowstasklistLista los procesos activos en consola
Windowstaskkill /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écnicaCómo funcionaEjemploVentajasInconvenientes
FicherosLeen y escriben en un mismo ficheroUno guarda un log y otro lo analizaSencillo y persistenteLento (disco) y riesgo de bloqueos
PipesLa salida de uno va a la entrada de otrols | grep ".java"Muy útil en Linux; sin ficheros temporalesSolo secuencial y no permanente
SocketsComunicación por red, local o remotaCliente-servidor con localhostFlexibles: local y distribuidoMás complejos: puertos y protocolos
Memoria compartidaVarios procesos acceden a la misma zona de memoriaSegmento de memoria compartidoMuy rápidaNecesita sincronización (condiciones de carrera)
Mensajes (colas)Se envían mensajes estructuradosCola de mensajes del SOAsíncrono, desacopla procesos, robustoMá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í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) 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.

Condición de carreraVarios procesos acceden y modifican un recurso compartido a la vez, y el resultado depende del orden de ejecución. El SO no garantiza ese orden y puede cambiar en cada ejecución.
Ejemplo 1: fichero de registro

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).

Ejemplo 2: cuenta bancaria

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

Bloqueo de ficheros

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.

Semáforos del sistema

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.

Colas de mensajes

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.

MecanismoFuncionamientoEjemplo de uso
Bloqueo de ficherosImpide que más de un proceso escriba en el fichero a la vezEditor de texto que bloquea el documento en uso
SemáforosControlan el acceso a un recurso compartido con un contadorControl de acceso a una impresora
Colas de mensajesEnvían señales de permiso o de finalizaciónUn 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étodoQué 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)
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.

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());
ls→ getInputStream()padre (Java)transferTo → getOutputStream()grep txt→ getInputStream()System.out
Sin outputStream.close()grep se queda esperando más datos y el programa se cuelga en 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());
}
Pipes vs Pipeline

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.

startPipeline tiene reglas

Solo el primero puede tener redirectInput y solo el último redirectOutput. Si no, IllegalArgumentException.

new ProcessBuilder("ls -l")

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.

PADREtu programa Java
p.getOutputStream()el padre escribe → le llega al hijo por System.in
HIJOlee de System.in
PADRElee
p.getInputStream()lo que el hijo imprime con System.out → lo lee el padre
HIJOSystem.out
PADRElee
p.getErrorStream()los errores del hijo (System.err)
HIJOSystem.err
Quiero…UsoEn la shell
Coger lo que imprime el hijop.getInputStream()—
Pasarle datos al hijop.getOutputStream() (y cerrarlo al acabar)—
Leer los errores del hijop.getErrorStream()—
Que el hijo lea un ficheropb.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 ficheropb.redirectOutput(Redirect.appendTo(new File("f")))>> f
Errores del hijo a un ficheropb.redirectError(new File("f"))2> f
Juntar errores con la salida normalpb.redirectErrorStream(true)2>&1
Errores del hijo en mi consolapb.redirectError(Redirect.INHERIT)—
Todo (entrada, salida y errores) en mi consolapb.inheritIO()—
La salida de A es la entrada de BProcessBuilder.startPipeline(List.of(a, b))a | b
Redirect.PIPE

Es el valor por defecto: el stream del hijo queda conectado con el padre y se usa con get…Stream().

Si rediriges a fichero…

…p.getInputStream() devuelve un stream vacío: la salida ya no pasa por el padre.

Cerrar la entrada

Si le pasas datos a sort, hay que cerrar el OutputStream. Si no, sort sigue esperando datos y nunca contesta.

En la prácticaEl primer filtro lee el extracto (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
    }
}
EjercicioLa línea que cambiaEn la shell
LaunchProcessRuntime.getRuntime().exec(PROGRAM) + waitFor()pluma
ListFiles (Runtime / ProcessBuilder)p.getInputStream().transferTo(System.out)ls -l
RedirectOutputredirectOutput(new File("output.txt"))ls -l > output.txt
AppendOutput extraredirectOutput(Redirect.appendTo(new File("log.txt")))date >> log.txt
RedirectInput extraredirectInput(new File("nombres.txt"))sort < nombres.txt
RedirectError extraredirectError(new File("error.txt"))ls /no-existe 2> error.txt
MergeErrors extraredirectErrorStream(true)2>&1
InheritIO extrainheritIO()el hijo usa tu consola
ReadOutput extranew BufferedReader(new InputStreamReader(p.getInputStream()))el padre lee
WriteInput extranew BufferedWriter(new OutputStreamWriter(p.getOutputStream())) + close()el padre escribe
Pipesp1.getInputStream().transferTo(p2.getOutputStream()) + close()ls | grep txt
PipelineProcessBuilder.startPipeline(processBuilders)ls | grep txt
DestroyProcess extraisAlive(), destroy(), destroyForcibly()kill / kill -9
WorkingDirectory extradirectory(new File("/tmp"))cd /tmp && ls
Environment extraenvironment().put("NOMBRE", "Ana")NOMBRE=Ana cmd
Códigos de salida

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).

Lo que no captura el catch

IllegalThreadStateException (exitValue antes de tiempo) e IllegalArgumentException (startPipeline mal montada) no son IOException: el programa se cae con la traza.

Si nadie recoge la salida…

…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 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

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

Se nombra desde el punto de vista de Java.

✗ 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.

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

Orden y argumentos separados.

✗ p.exitValue() justo después de start()✓ p.waitFor()

Si no ha acabado: IllegalThreadStateException.

✗ filtro > statement-01.txt✓ filtro < statement-01.txt

Con > vacías el extracto y el filtro espera teclado.

✗ redirectInput al 2º de la pipeline✓ solo al primero

startPipeline lanza IllegalArgumentException.

✗ escribir a sort y leer sin cerrar✓ cerrar el OutputStream antes de leer

Si no, sort espera más datos para siempre.

✗ Bloqueado = Preparado✓ Bloqueado espera un evento, Preparado espera CPU

Aunque le des CPU a uno bloqueado, no puede avanzar.

✗ RR con quantum enorme es justo✓ se comporta como FCFS

Nadie llega a agotar el quantum.

Preguntas tipo examen

rellena los desplegables
Comprobadas 0 de 0 preguntas · 0 / 0 huecos bien
Tipo
Formato
Tema