Algoritmos y Estructuras de Datos Compiladores e Intérpretes Herramientas Lenguaje de programación
!Prog C/C++
Linux Matemáticas
Mates Discretas
Programación Orientada a Objetos Redes y Computación Distribuida Sistemas Operativos

Hilos en Java

[date: 28-12-2024] [last modification: 31-12-2024]
[words: 811] [reading time: 4min] [size: 15330 bytes]

Repaso rápido de cómo funcionan los hilos de Java y cómo gestionar las posibles carreras críticas.

Nota

Conviene leer el artículo sobre Procesos e Hilos, sobre todo:

Y además comprender los problemas que pueden suceder con el uso de hilos, las carreras críticas.

La JVM permite planificar LWP en el kernel o en modo usuario con hilos de Java, en función de si la plataforma los soporta.

Por defecto, se crea un hilo principal a partir de la función main(), y el resto tienen un padre que los ha creado. El padre tiene una referencia para poder interactuar con ellos, deternerlos, etc.

Clase Thread

La clase java.lang.Thread implementa la interfaz java.lang.Runnable, es decir, una operación que no devuelve ningún valor.

Por tanto, para que el hilo realice una tarea, es necesario implementar void run() para darle sus instrucciones.

public class MiHilo extends Thread {
    public MiHilo(String nombre) {
        super(nombre);
    }

    @Override
    public void run() {
        // Trabajo útil...
    }
}

// ...

public static void main(String[] args) {
    MiHilo hilo = new MiHilo("NombreDelHilo");
    hilo.start();
}

En caso de que la clase ya tenga que ser hija de otra (y dado que en Java no hay herencia múltiple), es posible implementar directamente Runnable:

public class MiHilo extends OtraClase implements Runnable {
    @Override
    public void run() {
        // Trabajo útil...
    }
}

// ...

public static void main(String[] args) {
    Thread hilo = new Thread(new MiHilo(), "NombreDelHilo");
    hilo.start();
}

Otra alternativa, es pasarle el Runnable directamente:

Thread hilo = new Thread(new Runnable() {
    @Override
    public void run() {
        // Trabajo útil...
    }
}, "NombreDelHilo");

Y gracias a la interfaz funcional de Java, es posible simplificar toda esta sintaxis a lo siguiente:

Thread hilo = new Thread(() -> {
    // Trabajo útil...
});
Posibles estados de un hilo y sus transiciones

Posibles estados de un hilo y sus transiciones

Métodos

La clase Thread aporta:

Se puede obtener una referencia al hilo actual con Thread.currentThread(). Otro método estático es el de Thread.sleep(long milis), que bloquea el hilo.

Si todavía hay hilos ejecutándose, la JVM no terminará su ejecución. Sin embargo, si el hilo es de tipo deamon (void setDaemon(boolean) y boolean isDaemon()), terminará de todos modos, efectivamente matándolo.

Carreras críticas

Los hilos tienen su propia stack privada, pero pueden compartir datos mediante variables estáticas o recibiendo de parámetros referencias a otros objetos.

Esto es susceptible a carreras críticas por lo que es necesario proteger su acceso con candados. Nos interesa el siguiente mecanismo:

lock.close(); // Se bloquea si está cerrado
    // Sección crítica
lock.open(); // Libera el candado para el resto

En Java, esto está implementado a nivel del lenguaje: bloque synchronized:

synchronized (lock) {
    // Sección critica
}

Al inicio del bloque se realiza lock.close(), y cuando se sale del bloque, lock.open().

También se puede declarar un método como synchronized:

public synchronized void metodo() {
    // Cosas...
}

Que no es más que syntactic sugar para lo siguiente:

public void metodo() {
    synchronized (this) {
        // Cosas...
    }
}

Cuando una clase tiene sus métodos sincronizados, es prácticamente un monitor.

¿Qué objetos usamos de candado?

En Java, cualquier objeto en memoria puede ser un candado, dado que este mecanismo está implementado en la clase Object.

A mayores, tiene los métodos wait(), wait(long milis), notify() y notifyAll() que implementan variables de condición.

Conclusión
Por tanto, lo mejor es usar el objeto compartido o recurso directamente como candado.
Si la clase es el recurso, se puede usar un método synchronized.

Conjuntos de datos

No es necesario utilizar synchronized en todas partes cuando se usan las colecciones de Java, dado que existen unas versiones ya sincronizadas. A continuación, se muestra una tabla de equivalencia no exhaustiva.

No sincronizadoSincronizado
ArrayListVector
HashMapConcurrentHashMap
Anterior: Paso de Mensajes Volver a Redes y Computación Distribuida Siguiente: Llamadas a métodos remotos y Objetos Distribuidos