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
Métodos
La clase Thread aporta:
void start(): inicia el hilo para que ejecute su tarea ==> Es una llamada no bloqueante. Si ha sido exitoso,boolean isAlive()devuelvetrue. Importante: no sirve llamar arun()directamente, porque no se creará el hilo.void join(): bloquea el hilo actual hasta que el otro termine. Se le puede especificar un timeout.void yield(): cede la CPU en favor de otros hilos. Es un hint, por lo que no siempre funciona.void interrupt(): envía la excepciónInterruptedExceptional hilo. No hay forma de detenerlo, pero esta es una forma para enviarle una señal para que termine. Se puede comprobar también conboolean isInterrupted().String getName(): todos los hilos tienen un nombre, lo que lo hace útil luego para debuggear. También se puede obtener su ID:long threadId().
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.
wait()bloquea el hilo actual de forma indefinida y libera el candado. Solo tiene sentido cuando tiene el candado, por lo que debe ser llamado dentro de un bloquesynchronized.wait(long milis)hace lo mismo, pero lanza una excepción si pasa el tiempo especificado.El hilo retorna su ejecución cuando otro ejecuta a
notify(). En principio, no se sabe qué hilo despertará, pero solo lo hará 1.notifyAll()notifica a todos los hilos dormidos que ejecutaron unwait().
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 sincronizado | Sincronizado |
|---|---|
ArrayList | Vector |
HashMap | ConcurrentHashMap |