Hilos
Conceptos básicos y cómo funcionan los hilos:
Índice
Los hilos comparten el espacio de memoria del usuario, es decir corren dentro del contexto de otro programa; y los procesos generalmente mantienen su propio espacio de direcciones y entorno de operaciones. Por ello a los hilos se les conoce a menudo como procesos ligeros.
Evita que la carga y ejecución de los procesos sean más pesadas. Los usamos para realizar programas concurrentes (que no es lo mismo que paralelos).
Qué son?
Una secuencia de código en ejecución dentro del contexto de otro proceso. No pueden ejecutarse solos y puede haber uno o varios hilos en cada uno.
A partir del hilo principal podemos crear el resto de hilos que se ejecutarán paralelamente.
Se pueden usar para hacer tareas simultáneas que no tengan que detenerse entre sí.
Por ejemplo, un programa que controla sensores en una fábrica, cada sensor puede ser un hilo independiente y recoge un tipo de información; y todos deben controlarse de forma simultánea.
Clases para crear hilos
Hay dos clases que forman parte del paquete java.lang. Como no se pueden usar al mismo tiempo hay que escoger una u otra.
Extendiendo la clase Thread
Debe sobreescribirse su método run() con las acciones a desarrollar.
También existen las clases start() y stop() pero ya no se usan ahora porque no son seguras.
Crear el objeto hilo es lo habitual:
NombreHilo h = new NombreHilo();
//Instanciamos la ejecución
h.start();Ejemplo básico:
public class PrimerHilo extends Thread{
private int x;
PrimerHilo (int x){
this.x=x;
}
public void run() {
for (int i=0; i<x; i++){
System.out.println("En el Hilo...");
}
}
}Ejemplo con varios hilos, concretamente 4, ya que hay que contar el main como un hilo en proceso:
public class HiloEjemplo1 extends Thread {
private int c; // contador de cada hilo
private int hilo;
// constructor
public HiloEjemplo1(int hilo) {
this.hilo = hilo;
System.out.println("CREANDO HILO: " + hilo);
}
// metodo run
public void run() {
c = 0;
while (c <= 5) {
System.out.println("Hilo: " + hilo + " C = " + c);
c++;
}
}
public static void main(String[] args) {
HiloEjemplo1 h = null;
for (int i = 0; i < 3; i++) {
h = new HiloEjemplo1(i + 1); // crear hilo
h.start(); // iniciar hilo
}
System.out.println("3 HILOS CREADOS...");
} // main
} // claseEl main suele estar en otra clase
Métodos sobre los hilos en Java
| Método | Misión |
|---|---|
| start() | Hace que el hilo comience la ejecución; la máquina virtual de Java llama al método run() de este hilo. |
| boolean isAlive() | Comprueba si el hilo está vivo. |
| sleep(long milisegundos) | Hace que el hilo actualmente en ejecución pase a dormir temporalmente durante el número de milisegundos especificado. Puede lanzar la excepción InterruptedException. |
| run() | Constituye el cuerpo del hilo. Es llamado por el método start() después de que el hilo apropiado del sistema se haya inicializado. Si el método run() devuelve el control, el hilo se detiene. Es el único método de la interfaz Runnable. |
| String toString() | Devuelve una representación en formato cadena de este hilo, incluyendo el nombre del hilo, la prioridad, y el grupo de hilos. |
| long getId() | Devuelve el identificador del hilo. |
| void yield() | Hace que el hilo actual de ejecución pare temporalmente y permita que otros hilos se ejecuten. |
| String getName() | Devuelve el nombre del hilo. |
| setName(String name) | Cambia el nombre de este hilo, asignándole el especificado como argumento. |
| int getPriority() | Devuelve la prioridad del hilo. |
| setPriority(int p) | Cambia la prioridad del hilo al valor entero p. |
| void interrupt() | Interrumpe la ejecución del hilo. |
| boolean interrupted() | Comprueba si el hilo actual ha sido interrumpido. |
| Thread currentThread() | Devuelve una referencia al objeto hilo que se está ejecutando actualmente. |
| boolean isDaemon() | Comprueba si el hilo es un hilo Daemon. |
| setDaemon(boolean on) | Establece este hilo como hilo Daemon, o como hilo de usuario. |
| void stop() | Detiene el hilo. Este método está en desuso. |
Vamos a trabajar con la InterruptedException para gestionar los diferentes hilos.
Se pueden trabajar los hilos de forma conjunta, es lo que se denomina un poolde hilos.
Un Daemon es un servicio en segundo plano que está siempre disponible, como puede ser en el SO la cola de impresión.
Ejemplo detallado
public class HiloEjemplo2 extends Thread {
public void run() {
System.out.println("Dentro del Hilo: " + this.getName() +
", Prioridad: " + this.getPriority() +
", ID: " + this.getId());
}
public static void main(String[] args) {
HiloEjemplo2 h = null;
for (int i = 0; i < 3; i++) {
h = new HiloEjemplo2(); // crear hilo
h.setName("HILO" + i); // damos nombre al hilo
h.setPriority(i + 1); // damos prioridad
h.start(); // iniciar hilo
System.out.println("Informacion del " +
h.getName() + ": " + h.toString());
}
System.out.println("3 HILOS CREADOS...");
} // main
}// claseImplementando la interfaz Runnable
public class Reloj extends Applet implements Runnable {
// La clase implementa Runnable, por lo que debe definir el método run()
}
// Forma general de declarar un hilo implementando la interfaz Runnable
class NombreHilo implements Runnable {
// Propiedades, constructores y métodos de la clase
public void run() {
// Acciones que lleva a cabo el hilo
}
}
public class PrimerHiloR implements Runnable {
private int x;
PrimerHiloR(int x) {
this.x = x;
}
public void run() {
for (int i = 0; i < x; i++)
System.out.println("En el Hilo... " + i);
}
}Suspender un hilo
Lo hacemos con el método sleep(). No es que se detenga, pero lo deja dormido unos milisegundos. Por otra parte con suspend() lo paramos durante un tiempo indeterminado hasta que lo reanudes. aunque es un método obsoleto y puede provocar un bloqueo de otros procesos. Los interbloqueos pueden provocar una situación parecida a la de un bucle infinito. Se puede reanudar un hilo con resume() desde otro hilo, que también está en desuso. Enviamos señales entre hilos para gestionarlos.
Métodos en uso para suspender hilos:
Para suspender de forma segura el hilo se debe introducir en el hilo una variable, por ejemplo suspender y comprobar su valor dentro del método run(), es lo que se hace en la llamada al método suspender. waitForResume() del ejemplo siguiente. El método requestSuspend() del hilo da valor true a la variable para suspender el hilo.
El método wait() hace que el hilo espere hasta que le llegue un notify() o un notifyAII();
Wait() solo se puede llamar desde dentro de un método sincronizado (synchronized). Estos tres métodos se usan en sincronización de hilos.
Básicos sobre los hilos
- En la clase se extiende el Thread
- Override el método run()
- Crear instancias y lanzar los hilos
Lo complicado es ver con las especificaciones cuando vamos a modelar los programas para usar los hilos
Con los simuladores que vamos a usar intentaremos crear programas de simulación que trabajen con hilos, de forma que cada hilo sea una entidad que maneje el programa y que se pueda ejectuar de una forma u otra las diferentes operaciones.
Interrupción de hilos
Usamos el método isInterrupted() para consultar si un hilo está interrumpido. Ojo con los booleanos de interrupted o isInterrupted ya que uno se queda fijo en true y el otro inmediatamente vuelve a false.
Public class Main{
public static void main(String[] ars) {
HiloUno uno = new HiloUno();
Thread dos = new Thread(new HiloDos());
uno.start();
dos.start();
// Dormimos un tiempo el hilo para evitar que se bloquee rápido
Thread.sleep(Duration.ofSeconds(5));
uno.interrupt();
System.out.println("Fin de main");
}
}
Public class HiloUno extends Thread {
@Override
public void run(){
long i = 0;
for(;;){
// En este caso el boolean se usa una sola vez y se queda en true
// si no alicaramos luego el return seguriía diciendo que se ha interrumpido
boolean inter = isInterrupted();
// Aquí señalaría cada vez que ocurre porque luego vuelve el booleano
// a false...
boolean inter2 = Thread.interrupted();
if (inter) {
System.out.println("He sido ninterrumpido");
return;
}
if(++i == 100000000){
i = 0;
System.out.println("Hola desde hilo Uno");
}
}
}
}
public class HiloDos implements Runnable {
@Override
public void run() {
System.out.println("Hola desde HiloDos");
}
}Unión de hilos con Thread.join()
Para que un hilo espere a la finalización de otro, debe usarse la llamada a join(). Por ejemplo esperar a que una imagen esté correctamente procesada antes de comentarlo.
public class Main {
public static void main(String[] args) {
// Aquí lo llamamos con funciones lambda
Thread t = new Thread(() -> {
try {
System.out.println("Iniciamos");
Thread.sleep(Duration.ofSeconds(2));
System.out.println("Terminamos.");
} catch (InterruptedException ex){
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null);
}
});
t.start();
// Si ponemos join con una duración y t no ha finalizado, el principal
// continuaria. Si no, se congela el main hasta terminar el t
// es útil porque deja congelado el hilo y libera los recursos
try{
t.join();
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null);
}
System.out.println("Listo");
}
}Como usar Thread.join
Thread t = new Thread(() -> {
System.out.println("Iniciamos");
Thread.sleep(Duration.ofSeconds(2));
System.out.println("Terminamos");
} catch (InterruptedException ex){
Logger.getLogger(Main.class.getName().log(level.SEVERE,null));
});
t.start();
// Esta solución para mostrar el estado es ineficiente. El hilo sigue activo
// pero quita tiempo de CPU solo para ver cuando va a finalizar
while(t.isAlive){
// Este mensaje puede llegar a mostrarse una vez más antes de mostrar el otro mensaje
System.out.println("Todavía se está ejecutando");
}
System.out.println("Ya está listo")
// La forma ideal es hacerlo con join que no tiene tiempo de CPU hasta que el hilo
//t se termine
try{
t.join();
// Esto sirve para cuando esto falla, es decir el join no el t.
} catch (InterruptedException ex){
Logger.getLogger(Main.class.getName().log(level.SEVERE,null));
}El hilo t empieza, se pausa dos segundos y continua. Durante el uso de este hilo hubo dos hilos al mismo tiempo: el main y el Thread t.
Join con parámetros
Se puede usar join() con parámetros, aunque parece ser poco fiable. Puede usarse para desistir o para pasar algún mensaje al usuario si dura demasiado el hilo y no continua el programa por ello. El hilo sigue, pero el join() ejecuta el código siguiente a donde tenía que esperar.
t.join(5000); // Son milisegundos
t.join(Duration.ofSeconds(10)); // SegundosSección Crítica
Una sección crítica son un conjunto de acciones que deben tener garantizado su acceso en un orden concreto. Suele estar asociado a un recurso compartido para evitar que entren valores raros.
- Monitores
- Semáforos
- Señales
Corrupción de memoria cuando dos hilos concurren a la misma memoria sin comprobarla y eso da errores. La forma de organizarlos suele ser con los semáforos.
Ejemplo
Variable valor y tenemos un hilo que lo incrementa en 1. Imaginemos que otro hilo también lo haga. En un caso idílico ocurriría así.
Pero ¿qué ocurre si justo cuando el hilo uno está incrementando el dos accede al valor de la memoria que tenía el mismo valor 0? Al no haber control de acceso se quedó el valor en 1.