Definición de Hilos y Procesos
Un proceso es una unidad independiente en el sistema operativo para la asignación de recursos y planificación. Representa la ejecución de un programa con un conjunto de datos, con su propio espacio de direcciones que incluye secciones de texto, datos y pila, sin compartir memoria con otros procesos.
Un hilo es una entidad dentro de un proceso, consideraad la unidad mínima de planificación de la CPU. Los hilos en un proceso comparten el espacio de direcciones del proceso padre, lo que permite comunicación directa mediante variables globales, aunque requiere mecanismos de sincronización. Cada hilo tiene su propio contador de programa, conjunto de registros y pila para su ejecución autónoma.
Ventajas del Uso de Hilos
Los hilos se utilizan principalmente para:
- Incrementar la eficiencia: Los programas multihilo pueden ejecutar tareas en paralelo, aprovechando múltiples núcleos de CPU para acelerar el procesamiento.
- Mejorar la experiencia del usuario: En aplicaciones con interfaz gráfica, los hilos permiten manejar operaciones costosas como E/S de archivos o peticiones de red sin bloquear la interfaz.
- Simplificar la programación: Facilitan el diseño de aplicaciones que manejan múltiples tareas concurrentes.
- Optimizar recursos: Permiten un uso más eficiente de la CPU y memoria, especialmante en operaciones de E/S.
Métodos para Crear Hilos en Java
En Java, se pueden crear hilos mediante varias técnicas:
Extendiendo la Clase Thread
Se puede crear un hilo extendiendo java.lang.Thread y sobrescribiendo el método run(). Luego, se instancia la clase y se invoca start() para iniciar la ejecución del hilo.
public class HiloPersonalizado extends Thread {
@Override
public void run() {
System.out.println("Hilo en ejecución");
for (int contador = 0; contador < 100; contador++) {
System.out.println(Thread.currentThread().getName() + " contador=" + contador);
}
}
}
public class Ejemplo1 {
public static void main(String[] args) {
HiloPersonalizado hilo = new HiloPersonalizado();
hilo.start();
for (int indice = 0; indice < 10; indice++) {
System.out.println("Hilo principal: " + indice);
}
}
}
Otro ejemplo podría involurcar la venta de entradas con múltiples hilos:
public class VentaEntradas extends Thread {
private static int entradasRestantes = 100;
@Override
public void run() {
while (true) {
if (entradasRestantes > 0) {
System.out.println(Thread.currentThread().getName() + " está vendiendo la entrada número " + entradasRestantes);
entradasRestantes--;
} else {
break;
}
}
}
}
Implementando la Interfaz Runnable
Una alternativa más flexible es implementar java.lang.Runnable y pasar la instancia a un objeto Thread. Esto es útil porque Java no permite herencia múltiple, pero sí implementación de interfaces.
public class TareaRunnable implements Runnable {
@Override
public void run() {
for (int iterador = 0; iterador < 100; iterador++) {
System.out.println(Thread.currentThread().getName() + " iteración: " + iterador);
}
}
}
public class Ejemplo2 {
public static void main(String[] args) {
TareaRunnable tarea = new TareaRunnable();
Thread hilo1 = new Thread(tarea, "Hilo A");
Thread hilo2 = new Thread(tarea, "Hilo B");
hilo1.start();
hilo2.start();
for (int k = 0; k < 100; k++) {
System.out.println(Thread.currentThread().getName() + ": " + k);
}
}
}
En un escenario de venta de entradas con retraso:
public class VentaRunnable implements Runnable {
private int entradasDisponibles = 100;
@Override
public void run() {
while (true) {
if (entradasDisponibles > 0) {
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
entradasDisponibles--;
System.out.println(Thread.currentThread().getName() + " vendió una entrada, quedan " + entradasDisponibles);
} else {
break;
}
}
}
}
public class Ejemplo3 {
public static void main(String[] args) {
VentaRunnable venta = new VentaRunnable();
Thread ventana1 = new Thread(venta);
Thread ventana2 = new Thread(venta);
ventana1.setName("Ventanilla 1");
ventana2.setName("Ventanilla 2");
ventana1.start();
ventana2.start();
}
}
Usando FutureTask con Callable
Para tareas que requieren un valor de retorno, se puede usar Callable junto con FutureTask y Thread. Esto permite obtener resultados de ejecuciones asíncronas.
import java.util.concurrent.Callable;
public class CalculoCallable implements Callable<double> {
@Override
public Double call() throws Exception {
double resultado = 0;
for (int num = 0; num < 1000; num += 3) {
resultado += num;
}
return resultado;
}
}
</double>
import java.util.concurrent.FutureTask;
import java.util.concurrent.ExecutionException;
public class Ejemplo4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CalculoCallable calculo = new CalculoCallable();
FutureTask<double> tareaFutura = new FutureTask<>(calculo);
Thread hiloCalculo = new Thread(tareaFutura);
hiloCalculo.start();
Double valor = tareaFutura.get();
System.out.println("Resultado: " + valor);
}
}
</double>
Uso de Pools de Hilos
Los pools de hilos, como ExecutorService, proporcionan una gestión eficiente del ciclo de vida de los hilos y los recursos del sistema.
Métodos Comunes de la Clase Thread
La clase Thread incluye métodos esenciales:
start(): Inicia la ejecución del hilo, invocando el métodorun().run(): Contiene el código que ejecutará el hilo; debe ser sobrescrito.currentThread(): Devuelve una referencia al hilo actual en ejecución.join(): Bloquea el hilo actual hasta que el hilo objetivo finalice.sleep(long millis): Suspende el hilo actual por el tiempo especificado.isAlive(): Verifica si el hilo está activo.setName(String)ygetName(): Configuran y obtienen el nombre del hilo.yield(): Cede el control de la CPU a otros hilos.setPriority(int)ygetPriority(): Ajustan y consultan la prioridad del hilo.
Diferencias entre Runnable y Callable
Las interfaces Runnable y Callable tienen distinciones clave:
- Valor de retorno:
Runnable.run()no retorna nada, mientras queCallable.call()devuelve un valor genérico. - Manejo de excepciones:
Runnableno puede lanzar excepciones verificadas, peroCallablesí lo permite. - Casos de uso:
Runnablees ideal para tareas simples sin retorno;Callablese usa para cálculos o operaciones que necesitan resultados o lanzar excepciones. - Integración con Future: Solo
Callablepuede combinarse conFuturepara obtener resultados asíncronos.