Tutorial de Java

Scheduling

Anterior | Siguiente
Java tiene un Scheduler, una lista de procesos, que monitoriza todos los hilos que se están ejecutando en todos los programas y decide cuales deben ejecutarse y cuales deben encontrarse preparados para su ejecución. Hay dos características de los hilos que el scheduler identifica en este proceso de decisión. Una, la más importante, es la prioridad del hilo de ejecución; la otra, es el indicador de demonio. La regla básica del scheduler es que si solamente hay hilos demonio ejecutándose, la Máquina Virtual Java (JVM) concluirá. Los nuevos hilos heredan la prioridad y el indicador de demonio de los hilos de ejecución que los han creado. El scheduler determina qué hilos deberán ejecutarse comprobando la prioridad de todos ellos, aquellos con prioridad más alta dispondrán del procesador antes de los que tienen prioridad más baja.

El scheduler puede seguir dos patrones, preemptivo y no-preemptivo. Los schedulers preemtivos proporcionan un segmento de tiempo a todos los hilos que están corriendo en el sistema. El scheduler decide cual será el siguiente hilo a ejecutarse y llama al método resume() para darle vida durante un período fijo de tiempo. Cuando el hilo ha estado en ejecución ese período de tiempo, se llama a suspend() y el siguiente hilo de ejecución en la lista de procesos será relanzado (resume()). Los schedulers no-preemtivos deciden que hilo debe correr y lo ejecutan hasta que concluye. El hilo tiene control total sobre el sistema mientras esté en ejecución. El método yield() es la forma en que un hilo fuerza al scheduler a comenzar la ejecución de otro hilo que esté esperando. Dependiendo del sistema en que esté corriendo Java, el scheduler será de un tipo u otro, preemtivo o no-preemptivo.

Prioridades

El scheduler determina el hilo que debe ejecutarse en función de la prioridad asignada a cada uno de ellos. El rango de prioridades oscila entre 1 y 10. La prioridad por defecto de un hilo de ejecución es NORM_PRIORITY, que tiene asignado un valor de 5. Hay otras dos variables estáticas disponibles, que son MIN_PRORITY, fijada a 1, y MAX_PRIORITY, que tiene un valor de 10. El método getPriority() puede utilizarse para conocer el valor actual de la prioridad de un hilo.

Hilos Demonio

Los hilos de ejecución demonio también se llaman servicios, porque se ejecutan, normalmente, con prioridad baja y proporcionan un servicio básico a un programa o programas cuando la actividad de la máquina es reducida.

Los hilos demonio son útiles cuando un hilo debe ejecutarse en segundo plano durante largos períodos de tiempo.Un ejemplo de hilo demonio que está ejecutándose continuamente es el recolector de basura (garbage collector). Este hilo, proporcionado por la Máquina Virtual Java, comprueba las variables de los programas a las que no se accede nunca y libera estos recursos, devolviéndolos al sistema.

Un hilo puede fijar su indicador de demonio pasando un valor true al método setDaemon(). Si se pasa false a este método, el hilo de ejecución será devuelto por el sistema como un hilo de usuario. No obstante, esto último debe realizarse antes de que se arranque el hilo de ejecución (start()). Si se quiere saber si un hilo es un hilo demonio, se utilizará el método isDaemon().

Diferencia entre hilos y fork()

fork() en Unix crea un proceso hijo que tiene su propia copia de datos y código del padre. Esto funciona correctamente si no hay problemas de cantidad de memoria de la máquina y se dispone de una CPU poderosa, y siempre que se mantenga el número de procesos hijos dentro de un límite manejable, porque se hace un uso intensivo de los recursos del sistema. Los applets Java no pueden lanzar ningún proceso en el cliente, porque eso sería una fuente de inseguridad y no está permitido. Las aplicaciones y los applets deben utilizar hilos de ejecución.

La multitarea pre-emptiva tiene sus problemas. Un hilo puede interrumpir a otro en cualquier momento, de ahí lo de pre-emptive. Fácilmente puede el lector imaginarse lo que pasaría si un hilo de ejecución está escribiendo en un array, mientras otro hilo lo interrumpe y comienza a escribir en el mismo array. Los lenguajes como C y C++ necesitan de las funciones lock() y unlock() para antes y después de leer o escribir datos. Java también funciona de este modo, pero oculta el bloqueo de datos bajo la sentencia synchronized:

synchronized int MiMetodo();

Otro área en que los hilos son muy útiles es en los interfaces de usuario. Permiten incrementar la respuesta del ordenador ante el usuario cuando se encuentra realizando complicados cálculos y no puede atender a la entrada de usuario. Estos cálculos se pueden realizar en segundo plano, o realizar varios en primer plano (música y animaciones) sin que se dé apariencia de pérdida de rendimiento.

Ejemplo de animación

Este es un ejemplo de un applet, java1006.java, que crea un hilo de animación que nos presenta el globo terráqueo en rotación. Aquí se puede ver que el applet crea un hilo de ejecución de sí mismo, concurrencia. Además, animacion.start() llama al start() del hilo, no del applet, que automáticamente llamará a run():

import java.awt.*;
import java.applet.Applet;

public class java1006 extends Applet implements Runnable {
    Image imagenes[];
    MediaTracker tracker;
    int indice = 0;
    Thread animacion;

    int maxAncho,maxAlto; 
    Image offScrImage; // Componente off-screen para doble buffering 
    Graphics offScrGC; 

    // Nos indicará si ya se puede pintar 
    boolean cargado = false; 

    // Inicializamos el applet, establecemos su tamaño y 
    // cargamos las imágenes 
    public void init() { 
        // Establecemos el supervisor de imágenes 
        tracker = new MediaTracker( this ); 
        // Fijamos el tamaño del applet 
        maxAncho = 100; 
        maxAlto = 100; 
        imagenes = new Image[33]; 

        // Establecemos el doble buffer y dimensionamos el applet 
        try { 
            offScrImage = createImage( maxAncho,maxAlto ); 
            offScrGC = offScrImage.getGraphics(); 
            offScrGC.setColor( Color.lightGray ); 
            offScrGC.fillRect( 0,0,maxAncho,maxAlto ); 
            resize( maxAncho,maxAlto ); 
        } catch( Exception e ) { 
            e.printStackTrace(); 
            } 

        // Cargamos las imágenes en un array 
        for( int i=0; i < 33; i++ ) 
            { 
            String fichero =  
                new String( "Tierra"+String.valueOf(i+1)+".gif" ); 
            imagenes[i] = getImage( getDocumentBase(),fichero ); 
            // Registramos las imágenes con el tracker 
            tracker.addImage( imagenes[i],i ); 
            } 

        try { 
            // Utilizamos el tracker para comprobar que todas las 
            // imágenes están cargadas 
            tracker.waitForAll(); 
        } catch( InterruptedException e ) { 
            ; 
            } 
        cargado = true; 
        } 

    // Pintamos el fotograma que corresponda 
    public void paint( Graphics g ) { 
        if( cargado ) 
            g.drawImage( offScrImage,0,0,this ); 
        } 

    // Arrancamos y establecemos la primera imagen 
    public void start() { 
        if( tracker.checkID( indice ) ) 
            offScrGC.drawImage( imagenes[indice],0,0,this ); 
        animacion = new Thread( this ); 
        animacion.start(); 
        } 
     
    // Aquí hacemos el trabajo de animación 
    // Muestra una imagen, para, muestra la siguiente... 
    public void run() { 
        // Obtiene el identificador del thread 
        Thread thActual = Thread.currentThread(); 

        // Nos aseguramos de que se ejecuta cuando estamos en un 
        // thread y además es el actual 
        while( animacion != null  &&  animacion == thActual ) 
            { 
            if( tracker.checkID( indice ) ) 
                { 
                // Obtenemos la siguiente imagen 
                offScrGC.drawImage( imagenes[indice],0,0,this ); 
                indice++; 
                // Volvemos al principio y seguimos, para el bucle 
                if( indice >= imagenes.length ) 
                    indice = 0; 
                } 

            // Ralentizamos la animación para que parezca normal 
            try { 
                animacion.sleep( 200 ); 
            } catch( InterruptedException e ) { 
                ; 
                } 
            // Pintamos el siguiente fotograma 
            repaint(); 
            } 
        }
    }

En el ejemplo se pueden observar más cosas. La variable thActual es propia de cada hilo que se lance, y la variable animacion la estarán viendo todos los hilos. No hay duplicidad de procesos, sino que todos comparten las mismas variables; cada hilo de ejecución, sin embargo, tiene su pila local de variables, que no comparte con nadie y que son las que están declaradas dentro de las llaves del método run().

La excepción InterruptedExcepcion salta en el caso en que se haya tenido al hilo parado más tiempo del debido. Es imprescindible recoger esta excepción cuando se están implementando hilos de ejecución, tanto es así, que en el caso de no recogerla, el compilador generará un error.

Navegador

Home | Anterior | Siguiente | Indice | Correo