lunes, 7 de mayo de 2012

Procesos Multitarea

Los procesadores y sistemas operativos permiten realizar múltiples procesos de forma simultánea.

La Máquina Virtual Java es un sistema multihilo, es decir, es capaz de ejecutar varios hilos de ejecución simultáneamente. Los hilos se conocen como “Thread”. Un hilo se puede definir como un único flujo de ejecución dentro de un proceso. La JVM puede gestionar asignación de tiempos de ejecución, prioridades, etc., de forma similar a como se gestionan múltiples procesos en un sistema operativo. Sin embargo los hilos en Java se ejecutan dentro de la JVM, siendo este un proceso del sistema operativo, lo cual le obliga a compartir todos los recursos. A este tipo de procesos donde se comparten los recursos se les llama a procesos ligeros. Entonces, un proceso se compone de un conjunto de hilos o procesos ligeros.

Los hilos son bastante útiles para el desarrollo de software debido a que permiten que el flujo del programa sea dividido en múltiples partes. Cada una de estas partes se debe dedicar de forma independiente a una tarea específica.

Java ofrece soporte para la gestión de hilos mediante las siguientes clases e interfaces:

  • Thread. La clase Thread, es la clase responsable de producir hilos funcionales para otras clases. Para añadir la funcionalidad de hilo a una clase, se deriva la clase de Thread y se sobre escribe el método run. El método run, contiene el código de ejecución de un hilo. La clase Thread también define el métodos start el cual permite iniciar la ejecución del hilo.
  • Runnable. La interfaz Runnable proporciona la capacidad de añadir la funcionalidad de un hilo a una clase simplemente implementando la interfaz, en lugar de derivarla de la clase Thread, esto debido a que la clase que requiere la implementación del hilo, deba extender de alguna otra clase. Las clases que implementan la interfaz Runnable proporcionan un método run que debe ser ejecutado por otra instancia.
  • ThreadDeath. La clase ThreadDeath proporciona un mecanismo que permite hacer limpieza después de que un hilo haya sido finalizado de forma asíncrona. Cuando el método stop de un hilo es invocado, una instancia de ThreadDeath es lanzada por el hilo.
  • ThreadGroup. La clase ThreadGroup se utiliza para manejar un grupo de hilos. Esta clase un mecanismo para controlar de modo eficiente la ejecución de un conjunto de hilos. Esta clase proporciona métodos stop, suspend y resume para controlar la ejecución de todos los hilos pertenecientes al grupo. Los grupos de hilos también pueden contener otros grupos de hilos permitiendo una jerarquía anidada de hilos. Los hilos individuales tienen acceso al grupo pero no al padre del grupo.
  • Object. La clase Object proporciona métodos necesarios dentro de la arquitectura multihilo de Java. Estos métodos son wait, notify y notifyAll. El método wait hace que el hilo de ejecución espere en estado dormido hasta que se le notifique que continúe. El método notify informa a un hilo en espera de que continúe con su ejecución. El método notifyAll es similar a notify excepto que se aplica a todos los hilos en espera. Estos tres métodos solo pueden ser llamados desde un método o bloque sincronizado. Normalmente estos métodos se utilizan cuando hay ejecución multihilo, es decir, cuando un método espera a que otro método termine de hacer algo antes de poder continuar. El primer hilo espera hasta que otro hilo le notifique que puede continuar.
Creacion de Hilos

En Java hay dos formas básicas de creación de hilos. La primera forma es mediante el uso de la clase Threat y la segunda es mediante el uso de la interface Runnable.

Creación de hilos mediante la clase Thread

En este método es necesario crear una clase que herede de la clase Thread y sobrecargar el método run.

La sintaxis para crear un hilo con base en la clase Thread es la siguiente

public class Hilo extends Thread {

 public void run(){
  ..
 }
}

En el método run se implementa el código correspondiente a la acción que el hilo debe desarrollar. El método run no es invocado directa o explícitamente, debido a que los hilos se inician con el método start. La clase Thread implementa el método sleep que permite detener la ejecución por un tiempo establecido en milisegundos.

La siguiente implementación, permite crear tres hilos. El constructor de la clase HiloThread, recibe el nombre del hilo y un número de iteraciones que es utilizado en el método run, el cual se ejecuta al hacer el llamado al método start. Dentro del método run, simplemente se calcula la hora exacta en el que ejecuta la iteración y hace el llamado al método sleep, con el fin de hacer una pausa en milisegundos de la ejecución del hilo.

package Hilos;

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * @author Hector Florez
 */
public class HiloThread extends Thread {
 private int iteraciones;
 private Calendar calendario;
 
 public HiloThread(String nombre, int iteraciones){
  super(nombre);
  this.iteraciones=iteraciones;
 }
 
 public void run(){  
  System.out.println("Hilo: "+this.getName()+" iniciado");  
  for(int i=this.iteraciones; i>=1; i--){
   this.calendario=new GregorianCalendar();
   int hora=this.calendario.get(Calendar.HOUR);
   int minuto=this.calendario.get(Calendar.MINUTE);
   int segundo=this.calendario.get(Calendar.SECOND);
   int milisegundo=this.calendario.get(Calendar.MILLISECOND);
   System.out.println("\nIteracion numero "+i+" del hilo denominado "+this.getName());
   System.out.println("Hora de ejecucion: "+hora+":"+minuto+":"+segundo+":"+milisegundo);
   try {
    sleep(i*50);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }  
 }

 public static void main(String[] args) {
  HiloThread hilo1=new HiloThread("uno", 5);
  HiloThread hilo2=new HiloThread("dos", 3);
  HiloThread hilo3=new HiloThread("tres", 6);
  hilo1.start();
  hilo2.start();
  hilo3.start();
 }
}
Salida Estándar
Hilo: uno iniciado
Hilo: dos iniciado
Hilo: tres iniciado

Iteracion numero 6 del hilo denominado tres
Hora de ejecucion: 1:46:10:671

Iteracion numero 5 del hilo denominado uno
Hora de ejecucion: 1:46:10:671

Iteracion numero 3 del hilo denominado dos
Hora de ejecucion: 1:46:10:671

Iteracion numero 2 del hilo denominado dos
Hora de ejecucion: 1:46:10:812

Iteracion numero 4 del hilo denominado uno
Hora de ejecucion: 1:46:10:921

Iteracion numero 1 del hilo denominado dos
Hora de ejecucion: 1:46:10:921

Iteracion numero 5 del hilo denominado tres
Hora de ejecucion: 1:46:10:968

Iteracion numero 3 del hilo denominado uno
Hora de ejecucion: 1:46:11:109

Iteracion numero 4 del hilo denominado tres
Hora de ejecucion: 1:46:11:218

Iteracion numero 2 del hilo denominado uno
Hora de ejecucion: 1:46:11:265

Iteracion numero 1 del hilo denominado uno
Hora de ejecucion: 1:46:11:375

Iteracion numero 3 del hilo denominado tres
Hora de ejecucion: 1:46:11:421

Iteracion numero 2 del hilo denominado tres
Hora de ejecucion: 1:46:11:562

Iteracion numero 1 del hilo denominado tres
Hora de ejecucion: 1:46:11:671
Creación de hilos mediante la interface Runnable

En este método es necesario crear una clase que implemente la interface Runnable e implementar el método run.

La sintaxis para crear un hilo con base en la interface Runnable es la siguiente:

public class Hilo implements Runnable {

 public void run(){
  ..
 }
}

En el método run se implementa el código correspondiente a la acción que el hilo debe desarrollar. El método run no es invocado directa o explícitamente, debido a que los hilos se inician con el método start.

Hay dos desventajas claras de la interface Runnable con respecto a la clase Thread que consisten en que la interface Runnable no define un atributo de identificación del hilo y no implementa el método sleep.

La siguiente implementación, permite crear tres hilos. El constructor de la clase HiloRunnable, recibe el nombre del hilo y un número de iteraciones que es utilizado en el método run. Dentro del método run, simplemente se calcula la hora exacta en el que ejecuta la iteración. El nombre del hilo se deposita en el atributo nombre definido dentro de la clase. La interface Runnable, no implementa el método start, entonces para poder iniciar un hilo que implemente Runnable, se debe crear una instancia de la clase Thread enviándole en el constructor la instancia de la clase que implementa Runnable. A través de la instancia de la clase Thread, es posible iniciar el hilo ya que esta si cuenta con el método start.

package Hilos;

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * @author Hector Florez
 */
public class HiloRunnable implements Runnable {
 private int iteraciones;
 private Calendar calendario;
 private String nombre;

 public HiloRunnable(String nombre, int iteraciones){
  this.nombre=nombre;
  this.iteraciones=iteraciones;
 }
 
 @Override
 public void run() {
  System.out.println("Hilo: "+this.nombre+" iniciado");  
  for(int i=this.iteraciones; i>=1; i--){
   this.calendario=new GregorianCalendar();
   int hora=this.calendario.get(Calendar.HOUR);
   int minuto=this.calendario.get(Calendar.MINUTE);
   int segundo=this.calendario.get(Calendar.SECOND);
   int milisegundo=this.calendario.get(Calendar.MILLISECOND);
   System.out.println("\nIteracion numero "+i+" del hilo denominado "+this.nombre);
   System.out.println("Hora de ejecucion: "+hora+":"+minuto+":"+segundo+":"+milisegundo);
  }    
 }

 public static void main(String[] args) {
  HiloRunnable hilo1=new HiloRunnable("uno", 5);
  Thread t1 = new Thread(hilo1);
  t1.start();
  HiloRunnable hilo2=new HiloRunnable("dos", 3);
  Thread t2 = new Thread(hilo2);
  t2.start();
  HiloRunnable hilo3=new HiloRunnable("tres", 6);
  Thread t3 = new Thread(hilo3);
  t3.start();
 }
}
Salida Estándar
Hilo: uno iniciado
Hilo: dos iniciado
Hilo: tres iniciado

Iteracion numero 5 del hilo denominado uno
Hora de ejecucion: 2:41:58:562

Iteracion numero 4 del hilo denominado uno
Hora de ejecucion: 2:41:58:562

Iteracion numero 3 del hilo denominado uno
Hora de ejecucion: 2:41:58:562

Iteracion numero 2 del hilo denominado uno
Hora de ejecucion: 2:41:58:562

Iteracion numero 3 del hilo denominado dos
Hora de ejecucion: 2:41:58:578

Iteracion numero 2 del hilo denominado dos
Hora de ejecucion: 2:41:58:578

Iteracion numero 1 del hilo denominado dos
Hora de ejecucion: 2:41:58:578

Iteracion numero 1 del hilo denominado uno
Hora de ejecucion: 2:41:58:578

Iteracion numero 6 del hilo denominado tres
Hora de ejecucion: 2:41:58:578

Iteracion numero 5 del hilo denominado tres
Hora de ejecucion: 2:41:58:578

Iteracion numero 4 del hilo denominado tres
Hora de ejecucion: 2:41:58:578

Iteracion numero 3 del hilo denominado tres
Hora de ejecucion: 2:41:58:578

Iteracion numero 2 del hilo denominado tres
Hora de ejecucion: 2:41:58:578

Iteracion numero 1 del hilo denominado tres
Hora de ejecucion: 2:41:58:578
Agrupamiento de Hilos

Todo hilo en Java hace parte de un grupo de hilos. Los grupos de hilos proporcionan un mecanismo de agrupamiento de múltiples hilos dentro de un único objeto. Gracias a este agrupamiento, es posible manipular un conjunto de hilos en lugar de manipular cada hilo de forma individual. De esta forma, si se desea iniciar o suspender un conjunto de hilos, basta con hacer el proceso al grupo de hilos que los contiene.

Los grupos de hilos de Java están implementados por la clase ThreadGroup que pertenece al paquete Java.lang. En el momento de crear un hilo, la máquina virtual de Java, ubica en tiempo de ejecución dicho hilo por defecto en un grupo de hilos.

Un hilo hace parte de un grupo de hilos de forma permanente, lo cual no permite que un hilo pueda moverse de un grupo a otro una vez que ha sido creado.

La sintaxis para crear un grupo de hilos y para asignar hilos al grupo es la siguiente.

ThreadGroup grupo1 = new ThreadGroup("Grupo uno");
HiloThread hilo1 = new HiloThread(grupo1,"uno");
HiloThread hilo2 = new HiloThread(grupo1,"dos");
HiloThread hilo3 = new HiloThread(grupo1,"tres");
Sincronizacion

Existen casos en los que varios hilos requieren utilizar un mismo recurso. En el momento de acceder a dichos recursos, los hilos deben establecer un orden de acceso para poder hacer los diferentes procesos de forma correcta.

Para asegurar en la aplicación que hilos concurrentes no colisionan y operan correctamente con recursos compartidos, se crea un sistema que administre dicha concurrencia.

Las secciones de código de una aplicación que acceden a un mismo recurso desde dos hilos distintos se denominan “secciones críticas”. Para sincronizar dos o más hilos, se hace uso del modificador synchronized en aquellos métodos del recurso con los que puedan producirse situaciones de colisión. De esta forma, Java genera un bloqueo controlado con el recurso sincronizado.

La sintaxis para sincronizar un método es la siguiente

public synchronized void metodo() {
 ...
}

Existe un ejemplo clásico de sincronización en donde existe un productor y un consumidor. El productor no puede producir un recurso si existe otro. El consumidor no puede consumir un recurso si no existe. Entonces el productor produce un recurso y en ese mismo momento el consumidor puede consumirlo. Después que el consumidor lo consume, el productor puede producir uno nuevo.

La siguiente implementación resuelve este caso.

Clase Recurso
package Hilos;

/**
 * @author Hector Florez
 */
public class Recurso {
 private int dato;
 private boolean hayDato = false;

 public synchronized int get() {
  while (hayDato == false) {
   try {
   // espera a que el productor coloque un valor
   wait();
   } catch (InterruptedException e) { }
  }
  hayDato = false;
  // notificar que el valor ha sido consumido
  notifyAll();
  return dato;
 }
 
 public synchronized void put(int valor) {
  while (hayDato == true) {
   try {
    // espera a que se consuma el dato
    wait();
   } catch (InterruptedException e) { }
  }
  dato = valor;
  hayDato = true;
  // notificar que el valor ha sido producido.
  notifyAll();
 } 
}

La clase recurso, tiene dos métodos sincronizados que son get y put. Estos métodos retornan un dato y asignan un dato respectivamente. Un atributo denominado hayDato le permite esperar al método get hasta que exista dicho dato. Una vez lee el dato, envía una notificación. El atributo hayDato también permite al método put, esperar hasta que el dato no exista. Una vez colocado el nuevo dato, envía una notificación.

Clase Productor
package Hilos;

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * @author Hector Florez
 */
public class Productor extends Thread {
 private Recurso contenedor;
 
 public Productor (Recurso c) {
  contenedor = c;
 }
 
 public void run() {
  for (int i = 0; i < 10; i++) {
   contenedor.put(i);
   Calendar calendario=new GregorianCalendar();
   int hora=calendario.get(Calendar.HOUR);
   int minuto=calendario.get(Calendar.MINUTE);
   int segundo=calendario.get(Calendar.SECOND);
   int milisegundo=calendario.get(Calendar.MILLISECOND);
   System.out.println("Productor. Valor: " + i);
   System.out.println("Hora de ejecucion: "+hora+":"+minuto+":"+segundo+ ":"+milisegundo);   
   try {
    sleep(800);
   } catch (InterruptedException e) { }
  }
 }
}

La clase productor, hace un proceso iterativo donde invoca el método put de la clase recurso. Imprime la hora exacta en que hace el proceso y hace una interrupción de 800 milisegundos.

Clase Consumidor
package Hilos;

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * @author Hector Florez
 */
public class Consumidor extends Thread {
 private Recurso contenedor;
 
 public Consumidor (Recurso c) {
  contenedor= c;
 }
 
 public void run() {
  int dato = 0;
  while(true){
   dato = contenedor.get();
   Calendar calendario=new GregorianCalendar();
   int hora=calendario.get(Calendar.HOUR);
   int minuto=calendario.get(Calendar.MINUTE);
   int segundo=calendario.get(Calendar.SECOND);
   int milisegundo=calendario.get(Calendar.MILLISECOND);
   System.out.println("Consumidor. Valor: " + dato);
   System.out.println("Hora de ejecucion: "+hora+":"+minuto+":"+segundo+ ":"+milisegundo);   
  }
 }
}

La clase consumidor, hace un permanentemente un proceso iterativo donde invoca el método get de la clase recurso. Imprime la hora exacta en que hace el proceso.

Clase PruebaSincronizacion
package Hilos;

/**
 * @author Hector Florez
 */
public class PruebaSincronizacion {

 public static void main(String[] args) {
  Recurso c = new Recurso ();
  Productor produce = new Productor (c);
  Consumidor consume = new Consumidor (c);
  produce.start();
  consume.start();
 }
}

La clase prueba, crea un recurso un producto y un consumidor e invoca el método start de dichos hilos.

Los resultados son los siguientes.

Salida Estándar
Consumidor. Valor: 0
Hora de ejecucion: 2:5:22:734
Productor. Valor: 0
Hora de ejecucion: 2:5:22:734
Productor. Valor: 1
Hora de ejecucion: 2:5:23:531
Consumidor. Valor: 1
Hora de ejecucion: 2:5:23:531
Productor. Valor: 2
Hora de ejecucion: 2:5:24:328
Consumidor. Valor: 2
Hora de ejecucion: 2:5:24:328
Productor. Valor: 3
Hora de ejecucion: 2:5:25:125
Consumidor. Valor: 3
Hora de ejecucion: 2:5:25:125
Productor. Valor: 4
Hora de ejecucion: 2:5:25:937
Consumidor. Valor: 4
Hora de ejecucion: 2:5:25:937
Consumidor. Valor: 5
Hora de ejecucion: 2:5:26:734
Productor. Valor: 5
Hora de ejecucion: 2:5:26:734
Productor. Valor: 6
Hora de ejecucion: 2:5:27:531
Consumidor. Valor: 6
Hora de ejecucion: 2:5:27:531
Consumidor. Valor: 7
Hora de ejecucion: 2:5:28:328
Productor. Valor: 7
Hora de ejecucion: 2:5:28:328
Productor. Valor: 8
Hora de ejecucion: 2:5:29:140
Consumidor. Valor: 8
Hora de ejecucion: 2:5:29:140
Productor. Valor: 9
Hora de ejecucion: 2:5:29:937
Consumidor. Valor: 9
Hora de ejecucion: 2:5:29:937
Temporizadores

Un temporizador equivale a la ejecución de un hilo el cual ejecuta el método run en un intervalo de tiempo proporcionado.

La creación de un temporizador se hace mediante la creación de un objeto instancia de la clase Timer la cual pertenece al paquete java.util. Al objeto de la clase Timer se le asigna un objeto instancia de la clase TimerTask la cual pertenece al paquete java.util, con un tiempo de inicio y un tiempo de intervalo medido en milisegundos. Al instanciar la clase TimerTask, se puede implementar el método run que proporciona la acción que se va a ejecutar.

Esta implementación es bastante simple y útil, ya que no requiere crear un hilo mediante la herencia de Thread ni mediante la implementación de Runnable.

La siguiente implementación, crea un reloj a través de un Timer, al cual se le asigna un TimerTask con un intervalo de tiempo de 1000 milisegundos.

package Hilos;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Hector Florez
 */
public class Temporizador {
 private Timer timer;
 
 public static void main(String[] args) {
  Temporizador t=new Temporizador();
  t.iniciarTimer();
 }
 
 public void iniciarTimer(){
  this.timer=new Timer();
  TimerTask timerTask = new TimerTask(){ 
   public void run(){
    accion();
   } 
  }; 
  timer.scheduleAtFixedRate(timerTask, 0, 1000);       
 }

 private void accion(){
  Calendar calendario=new GregorianCalendar();
  int hora=calendario.get(Calendar.HOUR);
  int minuto=calendario.get(Calendar.MINUTE);
  int segundo=calendario.get(Calendar.SECOND);
  System.out.println("Hora: "+hora+":"+minuto+":"+segundo);   
 }
}

Los resultados son los siguientes.

Salida Estandar
Hora: 2:24:25
Hora: 2:24:26
Hora: 2:24:27
Hora: 2:24:28
Hora: 2:24:29
Hora: 2:24:30
Hora: 2:24:31
Hora: 2:24:32
Hora: 2:24:33
Hora: 2:24:34
Hora: 2:24:35
Hora: 2:24:36
Hora: 2:24:37
Hora: 2:24:38
Hora: 2:24:39
Hora: 2:24:40

No hay comentarios:

Publicar un comentario