Tabla de contenido
¿Qué es la programación con threads?
Un hilo de ejecución, hebra, threads o subprocesos es la unidad de procesamiento más pequeña que puede ser planificada por un sistema operativo.
La creación de un nuevo hilo es una característica que permite a una aplicación realizar varias tareas a la vez. Los distintos hilos de ejecución comparten una serie de recursos tales como el espacio de memoria, los archivos abiertos, situación de autenticación, etc. Esta técnica simplifica el diseño de una aplicación que debe llevar a cabo distintas funciones simultáneamente.
Un hilo es simplemente una tarea que puede ser ejecutada al mismo tiempo con otra tarea.
Los hilos de ejecución que comparten los mismos recursos, sumados a estos recursos, son en conjunto conocidos como un proceso. El hecho de que los hilos de ejecución de un mismo proceso compartan los recursos hace que cualquiera de estos hilos pueda modificar dichos recursos.
Estados de un thread
Un thread tiene un ciclo de vida que va desde su creación hasta su terminación. Durante su ciclo de vida cada uno de los hilos o tareas de una aplicación puede estar en diferentes estados como pueden ser:
• Nacido: Cuando se acaba de crear un hilo, se dice que está nacido, y continúa en ese estado hasta que se invoca el método start() del hilo. La siguiente sentencia crea un nuevo thread pero no lo arranca, por lo tanto deja el thread en el estado de nacido.
Cuando un thread está en este estado, es sólo un objeto Thread vacío o nulo. No se han asignado recursos del sistema todavía para el thread. Así, cuando un thread está en este estado, lo único que se puede hacer es arrancarlo con start().
• Listo: Cuando se invoca el método start() del hilo, se dice que está en estado listo. El método se arranca con la siguiente instrucción.
• Ejecutable: cuando el método start() se ejecuta, crea los recursos del sistema necesarios para ejecutar el thread, programa el thread para ejecutarse, y llama al método run() del thread que se ejecuta en forma secuencial. En este punto el thread está en el estado ejecutable. Se denomina así puesto que todavía no ha empezado a ejecutarse.
• En ejecución: Un hilo en estado de listo de la más alta prioridad, pasa al estado de ejecución, cuando se le asignan los recursos de un procesador, o sea cuando inicia su ejecución. Aquí el thread está en ejecución.Cada hilo tiene su prioridad, hilos con alta prioridad se ejecutan preferencialmente sobre los hilos de baja prioridad.
• No ejecutable :Un hilo continúa la ejecución de su método run(), hasta que pasa al estado de no ejecutable originado cuando ocurre alguno de los siguientes cuatro eventos:
Se invoca a su método sleep().
Se invoca a su su método suspend().
El thread utiliza su método wait() para esperar una condición variable.
El thread está bloqueado durante una solicitud de entrada/salida.
• Muerto: Un thread pasa al estado de muerto cuando se termina su método run(), o cuando se ha invocado su método stop(). En algún momento el sistema dispondrá entonces del hilo muerto. Un hilo puede morir de dos formas:
– Muerte natural: se produce cuando su método run() sale normalmente.
– Por muerte provocada: en cualquier momento llamando a su método stop(). El método stop() lanza un objeto ThreadDeath hacia al hilo a eliminar. El thread moririá cuando reciba realmente la excepción ThreadDeath.
• Bloqueado: un thread se encuentra en el estado bloqueado cuando el hilo realiza una solicitud de entrada/salida. Cuando termina la entrada/salida que estaba esperando, un hilo bloqueado queda en el estado listo.
Prioridades de los Threads:
La prioridad de un hilo se utiliza para determinar cúando se pasa a ejecutar otro hilo, lo cual se
denomina cambio de contexto de ejecución. Hilos con alta prioridad se ejecutan preferencialmente sobre hilos de menor prioridad.
La clase Thread tiene tres constantes predefinidas para asignar la prioridad:
MIN_PRIORITY (prioridad 1)
NORM_PRIORITY (prioridad 5)
MAX_PRIORITY (prioridad 10).
Los procesos a nivel de usuario se recomienda que tengan una prioridad alrededor de 5, tareas en segundo plano así como de redibujo de la pantalla o entrada/salida por la red, con una prioridad cercana a la minima.
Las reglas que determinan cuando se hace un cambio de prioridad son:
• Un hilo le cede voluntariamente el control a otro, por ejemplo, cuando abandona explícitamente, al quedarse dormido, o bloqueado en espera de una operación de entrada/salida pendiente. Se seleccionara para la CPU aquel hilo que estando preparado para ejecución tenga la prioridad más alta.
• Un hilo es desalojado por otro de prioridad más alta, independientemente de lo que el hilo desalojado estuviera haciendo.
• Si dos o más threads de igual prioridad compiten por el recurso, se pueden repartir el tiempo de la CPU mediante algoritmos especiales (algoritmo circular o round-robin). Sin embargo se pueden presentar problemas, ante la igualdad de prioridad.
Sincronización de hilos
Cuando en un programa tenemos varios threads corriendo simultáneamente es posible que varios hilos intenten acceder a la vez a un mismo sitio (un fichero, una conexión, un array de datos) y es posibible que la operación de uno de ellos entorpezca la del otro. Para evitar estos problemas, hay que sincronizar los hilos.
Solo uno de los hilos puede ser el propietario de un recurso en un instante dado. Los restantes hilos que estuviesen intentando acceder al monitor bloqueado quedan en suspenso hasta que el hilo propietario salga de dicho recurso. La manera de acceder al recurso es llamando a un método marcado con la palabra clave synchronized.
Durante todo el tiempo en que un hilo permanezca en un método sincronizado, los demás threads que intenten llamar a un método sincronizado tendrán que esperar. Para salir del recurso y permitir el control del objeto al siguiente hilo en espera.
Ejemplos de threads
Cada thread imprime una frase o mensaje en un momento determinado de la ejecución del programa (como puede ser al cambiar de estado).
Creación de múltiples hilos
class NewThread implements Runnable {
String name; // nombre del hilo
Thread t;
NewThread(String threadname) {
name = threadname; //dentro de la variable name se mete el nombre threadname
t = new Thread(this, name); //declaramos el hilo dentro de la variable t
System.out.print("Nuevo hilo: " + t + ", ");
t.start(); // Comienza el hilo
}
// Este es el punto de entrada del hilo.
public void run() {
try { //se intenta ejecutar lo siguiente
for(int i = 5; i > 0; i--) { //bucle que hace una cuenta atrás desde 5
System.out.println(name + ": " + i);
Thread.sleep(1000); //tiempo de espera en el que el hilo está dormido
}
}catch (InterruptedException e) { //control de error de interrupción
System.out.println(name + "Interrupción del hilo hijo" +name);
}
System.out.print("Sale del hilo hijo" + name +", ");
}
}
class MultiThreadDemo {
public static void main(String args[]) {
new NewThread("Uno"); // comienzan los hilos
new NewThread("Dos");
new NewThread("Tres");
try {
// espera un tiempo para que terminen los otros hilos
Thread.sleep(10000);
} catch (InterruptedException e) {
//en caso de error se imprime lo siguiente
System.out.println("Interrupción del hilo principal");
}
System.out.println("Sale del hilo principal.");
}
RESULTADO:
Nuevo thread: Thread[Uno,5], Nuevo hilo: Thread[Dos,5], Nuevo hilo: Thread[Tres,5]
Uno: 5
Dos: 5
Tres: 5
Uno: 4
Dos: 4
Tres: 4
Uno: 3
Dos: 3
Tres: 3
Uno: 2
Dos: 2
Tres: 2
Uno: 1
Dos: 1
Tres: 1
Sale del hilo.Uno, Sale del hilo.Dos, Sale del hilo.Tres, Sale del hilo principal.