Serie de instrucciones guardada como archivo en disco. Está «quieto».
Teoría
8 temas01Conceptos básicos
Un programa en ejecución. Vive en la RAM y usa la CPU.
Unidad mínima de ejecución dentro de un proceso. Comparte memoria con los otros hilos.
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
| 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 |
Todo proceso tiene como mínimo 1 hilo. Con 1 hilo es monohilo; con varios, multihilo.
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.
Condiciones de carrera, interbloqueos, y un fallo afecta a todo el proceso.
02Estados 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
Hay más procesos que CPUs, así que el planificador decide quién se ejecuta y cuándo.
| Tipo | Qué hace |
|---|---|
| Largo plazo | Pasa programas del disco a memoria (los convierte en procesos). |
| Medio plazo | Suspende 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
| 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 |
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).
| Paralela | Distribuida | |
|---|---|---|
| Dónde | Varios núcleos de un mismo ordenador | Varios ordenadores en red |
| Memoria | Compartida (misma RAM) | Cada máquina la suya |
| Comunicación | Memoria compartida | Por red |
| Ejemplo | Procesar una imagen con 4 núcleos | Google Search, torrent (P2P), juegos online |
Problemas típicos
Dos hilos tocan la misma variable a la vez y el resultado depende del orden → incorrecto.
Cada uno espera un recurso que tiene bloqueado el otro → parados para siempre.
Uno de baja prioridad nunca llega a ejecutarse.
04Gestión de procesos e IPC
PID: identificador único del proceso. PPID: PID del proceso padre.
| Orden | Para qué |
|---|---|
ps / ps -ef | Procesos activos, con PID y PPID |
top / htop | Lista en tiempo real (htop es más visual) |
kill PID | Envía SIGTERM: pide que pare |
kill -9 PID | Envía SIGKILL: lo mata al instante |
jobs, fg, bg | Primer y segundo plano en la shell |
jps -l | Procesos Java con su PID y clase (lo usamos en la práctica) |
| Windows: Administrador de tareas | Ver y finalizar procesos |
tasklist | Listar 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.
ls | grep .java: la salida de un proceso es la entrada del otro.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) |
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).
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étodo | Qué 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 |
getInputStream() 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();
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étodo | Qué 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++; } }
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 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
run() no crea hilo nuevo.
Se nombra desde el punto de vista de Java.
Al despertar hay que volver a comprobar.
Si no: IllegalMonitorStateException.
sleep solo pausa y libera la CPU.
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.
Lee, suma y escribe: otro hilo se mete en medio.
Y stop() no se usa: deja recursos abiertos.
MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY.
Orden y argumentos separados.