Estos apuntes están diseñados para ayudarte a preparar temas de programación concurrente con hilos en Java, especialmente en un entorno académico como 2º de DAM (Desarrollo de Aplicaciones Multiplataforma). Incluyen desde conceptos básicos hasta técnicas avanzadas que pueden ser evaluadas en un examen.
1. ¿Qué es un Hilo en Java?
Un hilo (thread) es la unidad más pequeña de procesamiento que puede ejecutarse de forma concurrente dentro de un programa. Cada hilo tiene su propia secuencia de instrucciones, pero comparte memoria y recursos con otros hilos dentro del mismo proceso.
Características clave:
- Multitarea: Permiten ejecutar varias tareas de manera simultánea.
- Hilo principal: El programa comienza con un hilo llamado
main
. - Estados del hilo:
- Nuevo (New): El hilo ha sido creado, pero no ha comenzado (
new Thread()
). - Ejecutable (Runnable): El hilo está listo para ejecutarse después de llamar a
start()
. - Ejecutando (Running): El hilo está ejecutándose.
- Bloqueado/Esperando (Blocked/Waiting): El hilo está esperando por un recurso o señal.
- Terminado (Terminated): El hilo ha finalizado.
- Nuevo (New): El hilo ha sido creado, pero no ha comenzado (
2. Crear y Usar Hilos en Java
2.1 Extender la clase Thread
class MiHilo extends Thread {
@Override
public void run() {
System.out.println("Hilo en ejecución: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
MiHilo hilo = new MiHilo();
hilo.start(); // Inicia el hilo
}
}
- Método
run()
: Contiene el código que ejecutará el hilo. - Método
start()
: Inicia el hilo y llama arun()
en un nuevo hilo de ejecución.
2.2 Implementar la interfaz Runnable
class MiTarea implements Runnable {
@Override
public void run() {
System.out.println("Tarea en ejecución: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
Thread hilo = new Thread(new MiTarea());
hilo.start(); // Inicia el hilo
}
}
- Usar
Runnable
es preferible si la clase debe heredar de otra, ya que Java no permite herencia múltiple.
3. Sincronización de Hilos
Cuando varios hilos acceden a un recurso compartido, puede haber problemas como condiciones de carrera. Para evitarlas, usamos sincronización.
3.1 Bloques Síncronos con synchronized
class Contador {
private int contador = 0;
public synchronized void incrementar() {
contador++;
System.out.println(Thread.currentThread().getName() + " - Contador: " + contador);
}
}
public class Main {
public static void main(String[] args) {
Contador contador = new Contador();
Runnable tarea = contador::incrementar;
Thread hilo1 = new Thread(tarea, "Hilo-1");
Thread hilo2 = new Thread(tarea, "Hilo-2");
hilo1.start();
hilo2.start();
}
}
3.2 Métodos wait()
y notify()
Se utilizan para coordinar hilos que comparten un recurso. Solo pueden ser llamados dentro de bloques sincronizados.
Ejemplo: Productor-Consumidor
class Almacen {
private int producto = -1;
private boolean disponible = false;
public synchronized void producir(int valor) throws InterruptedException {
while (disponible) {
wait(); // Espera si ya hay un producto
}
producto = valor;
disponible = true;
System.out.println("Producido: " + producto);
notify(); // Notifica al consumidor
}
public synchronized int consumir() throws InterruptedException {
while (!disponible) {
wait(); // Espera si no hay producto disponible
}
disponible = false;
System.out.println("Consumido: " + producto);
notify(); // Notifica al productor
return producto;
}
}
public class Main {
public static void main(String[] args) {
Almacen almacen = new Almacen();
Thread productor = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
almacen.producir(i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumidor = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
almacen.consumir();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
productor.start();
consumidor.start();
}
}
4. Gestión de Hilos con ExecutorService
En lugar de gestionar manualmente los hilos, puedes usar ExecutorService
para trabajar con grupos de hilos.
Ejemplo: Pool de Hilos
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService ejecutor = Executors.newFixedThreadPool(3); // Pool de 3 hilos
for (int i = 1; i <= 10; i++) {
final int tarea = i;
ejecutor.execute(() -> {
System.out.println("Ejecutando tarea " + tarea + " en " + Thread.currentThread().getName());
});
}
ejecutor.shutdown(); // Cierra el pool de hilos
}
}
5. Errores Comunes al Trabajar con Hilos
- No usar
start()
para iniciar un hilo:- Usar
run()
directamente ejecuta el código en el hilo principal.
- Usar
- Problemas de sincronización:
- No sincronizar recursos compartidos puede causar condiciones de carrera.
- Bloqueos (Deadlocks):
- Ocurren cuando dos hilos esperan indefinidamente por recursos que el otro posee.
- No manejar interrupciones:
- Siempre usa
Thread.currentThread().interrupt()
para manejar correctamente hilos interrumpidos.
- Siempre usa
6. Resumen y Puntos Clave para un Examen
- Estados del hilo: Asegúrate de conocer el ciclo de vida completo de un hilo.
- Crear hilos:
- Extender
Thread
. - Implementar
Runnable
.
- Extender
- Sincronización:
synchronized
para evitar conflictos.wait()
ynotify()
para coordinar hilos.
- Herramientas avanzadas:
ExecutorService
para gestionar grupos de hilos.
Con estos apuntes, deberías estar preparado para resolver cualquier problema sobre hilos en un examen de 2º DAM. ¡Buena suerte!