En esta sección, abordaremos conceptos clave y preguntas comunes en entrevistas sobre la programación concurrente en Java.
¿Qué es un Hilo (Thread)?
Un hilo se considera un proceso ligero. Permite que una aplicación realice múltiples tareas simultáneamente.
Componentes de un Hilo
- Procesador (CPU)
- Código ejecutable
- Datos asociados
¿Por qué utilizar Múltiples Hilos?
- Mejorar la capacidad de respuesta de la interfaz de usuario (UI).
- Aprovechar sistemas con múltiples procesadores.
- Simplificar la modelización de problemas complejos.
Implementación de Hilos en Java
Existen dos enfoques principales para crear hilos en Java:
1. Implementando la interfaz Runnable
class MiTareaRunnable implements Runnable {
@Override
public void run() {
// El bucle while(true) es común aquí para tareas continuas
System.out.println("Ejecutando desde Runnable.");
}
}
2. Heredando de la clase Thread
class MiTareaThread extends Thread {
@Override
public void run() {
System.out.println("Ejecutando desde Thread heredado.");
}
}
Ejemplo de uso:
public class DemostracionHilos {
public static void main(String[] args) {
MiTareaRunnable r = new MiTareaRunnable();
Thread hilo1 = new Thread(r);
Thread hilo2 = new Thread(r);
hilo1.start(); // Inicia el hilo 1
hilo2.start(); // Inicia el hilo 2
MiTareaThread hilo3 = new MiTareaThread();
hilo3.start(); // Inicia el hilo 3
}
}
Gestión y Pausa de Hilos
- Planificación (Scheduling): Se gestiona mediante métodos como
wait()ynotify(). - Pausa temporal: El método
sleep()detiene la ejecución de un hilo por un período específico.
Prioridad de Hilos
La prioridad de un hilo determina la importancia relativa de este hilo frente a otros hilos en la ejecución. Un hilo con mayor prioridad tiene más probabilidades de ser ejecutado antes.
Diferencias entre sleep() y wait()
sleep(): Suspende la ejecución del hilo actual durante un tiempo determinado, pero el hilo aún mantiene la posesión de los bloqueos (locks) que pueda tener.wait(): Libera la posesión de los bloqueos del objeto y pone el hilo en estado de espera hasta que otro hilo invoquenotify()onotifyAll()sobre el mismo objeto.
Hilos de Demonio (Daemon Threads)
Un hilo de demonio es un hilo de servicio que se ejecuta en segundo plano. El programa principal no espera a que los hilos de demonio terminen su ejecución para finalizar. Si solo quedan hilos de demonio ejecutándose, la JVM se cerrará.
Recursos Críticos (Critical Resources)
Son aquellos recursos (datos, archivos, dispositivos) que son compartidos por múltiples hilos y que, si no se gestionan adecuadamente, pueden llevar a condiciones de carrera o inconsistencias.
Exclusión Mutua (Mutex) y Bloqueos (Locks) en Java
La exclusión mutua garantiza que solo un hilo pueda acceder a un recurso compartido a la vez. En Java, esto se implementa comúnmente usando:
- La palabra clave
synchronizedaplicada a un bloque de código: Restringe la ejecución de ese bloque a un solo hilo a la vez. - La palabra clave
synchronizedaplicada a un método: Hace que todo el método sea accesible por un solo hilo a la vez.
Interbloqueo (Deadlock) y Prevención
Un interbloqueo ocurre cuando dos o más hilos quedan bloqueados indefinidamente, esperando cada uno un recurso que el otro hilo está manteniendo. Para evitarlo:
- Asegurar un orden consistente en la adquisición de bloqueos en todos los hilos.
- Minimizar el uso de recursos críticos compartidos.
- Utilizar timeouts en las operaciones de adquisición de bloqueos.
Uso de wait(), notify() y notifyAll()
wait(): Un hilo invoca este método sobre un objeto bloqueado para liberar el bloqueo y entrar en estado de espera.notify(): Despierta a un único hilo que esté esperando en la cola de espera de ese objeto.notifyAll(): Despierta a todos los hilos que estén esperando en la cola de espera de ese objeto.
Conceptos de Red Básicos
- URL (Uniform Resource Locator): Un localizador uniforme de recursos que identifica una fuente en Internet.
- IP (Internet Protocol): Dirección numérica que identifica de forma única un dispositivo en una red.
- Port (Puerto): Un número que identifica una aplicación o servicio específico en un dispositivo de red.
- TCP (Transmission Control Protocol): Un protocolo de conexión que garantiza la entrega fiable y ordenada de datos.
- UDP (User Datagram Protocol): Un protocolo sin conexión que ofrece una transmisión de datos más rápida pero sin garantía de entrega o orden.
Modelo de Red en Java
Java proporciona APIs para modelar la comunicación cliente-servidor, permitiendo la creación de aplicaciones de red robustas.
Programación de Red en Java
Los pasos generales para la programación de red en Java son:
- Establecer la conexión: El servidor se vincula a un puerto y el cliente se conecta a una dirección IP y puerto.
- Preparar datos para enviar: Escribir datos en un flujo de salida (OutputStream).
- Procesar datos recibidos: Leer datos de un flujo de entrada (InputStream) y darles formato según sea necesario.
- Cerrar la conexión: Liberar los recursos de red.
El servidor utiliza ServerSocket para escuchar conexiones entrantes y Socket para comunicarse con los clientes. El cliente utiliza Socket para conectarse al servidor.
Ejemplo de Programación Basada en Socket (Servidor)
import java.io.*;
import java.net.*;
public class ServidorSocket {
public static void main(String[] args) {
try {
ServerSocket servidor = new ServerSocket(8888); // Escucha en el puerto 8888
System.out.println("Servidor iniciado. Esperando conexiones...");
while (true) {
Socket clienteSocket = servidor.accept(); // Acepta una conexión entrante
System.out.println("Cliente conectado desde: " + clienteSocket.getInetAddress());
OutputStream salida = clienteSocket.getOutputStream();
DataOutputStream datosSalida = new DataOutputStream(salida);
datosSalida.writeUTF("¡Hola, cliente! Tu IP es " + clienteSocket.getInetAddress() + " y tu puerto es " + clienteSocket.getPort() + "\nAdiós!");
datosSalida.close();
clienteSocket.close();
}
} catch (IOException e) {
System.err.println("Error en el servidor: " + e.getMessage());
}
}
}
Programación Basada en UDP
(Se omitió el ejemplo de código UDP en el original, pero conceptualmente implica el uso de DatagramSocket y DatagramPacket para enviar y recibir paquetes de datos sin conexión).
Diferencias Clave entre TCP y UDP
- TCP: Proporciona un servicio de comunicación fiable, asegurando que los datos se entreguen en el orden correcto y sin pérdidas. Es más lento debido a los mecanismos de control de errores y flujo.
- UDP: Ofrece un servicio de comunicación no fiable y sin conexión. Es más rápido ya que no tiene sobrecarga de establecimiento de conexión ni confirmaciones, pero no garentiza la entrega ni el orden de los paquetes.