Prioridad entre hilos

Los sistemas operativos difieren en la forma de planificar la ejecución de los hilos. Windows gestiona el reparto de los tiempos de ejecución de los hilos (tareas) de forma directamente proporcional a la prioridad asignada a cada hilo. 

Ello significa la concesión de una cantidad de tiempo de ejecución a un hilo; finalizado ese tiempo el sistema concede la ejecución a otro hilo. Así, de forma circular el sistema va asignando los tiempos de CPU a los hilos, con mayor tiempo a los hilos con mayor prioridad. De esta forma, los hilos con mayor prioridad se ejecutan primero, ya que proporcionalmente se les asigna mayor tiempo de ejecución. Esto no significa que un hilo con mayor prioridad se ejecute hasta terminar, sino que los tiempos de CPU se reparten proporcionalmente según la prioridad que tengan. Hay que tener en cuenta que un hilo, aunque tenga mayor prioridad, pierde el control de la ejecución si efectúa una llamada a sleep(); también puede ceder ese control si llama a yield().

Java asigna, por omisión, la prioridad 5 (Thread.NORM_PRIORITY) a un hilo cuando este se crea. El rango de valores de la prioridad está determinado por las constantes: Thread.MIN_PRIORITY, Thread.MAX_PRIORITY. La prioridad de un hilo se puede modificar con el método de la clase Thread:

       public final void setPriority(int prioridad)

Para obtener la prioridad de un hilo se utiliza el método de Thread:

       public final int getPriority()

Por razones de seguridad, no se permite a un applet modificar la prioridad de un hilo.

Hilos daemon

Un hilo «demonio» tiene como propósito realizar servicios a otros hilos de ejecución. Los hilos demonio finalizan cuando todos los hilos «no demonio» finalizan, o bien cuando termina el método run() asociado. El ejemplo típico de hilo demonio es el «recolector de basura» (garbage collection), el cual se ejecuta en paralelo con el resto de hilos de la aplicación. El método, de la clase Thread, setDaemon() aplicado a un hilo, con el argumento true, después de su creación lo define como demonio.

Ejemplo

       Thread hilo = new ClaseHilo();
        hilo.setDaemon(true);

Un «demonio» se puede considerar un hilo de servicio. Su existencia es para suministrar algún tipo de servicio a otros hilos, por eso si los hilos a los que dan servicio desaparecen, también desaparecen los demonios.

Sincronización

En las aplicaciones es normal que existan muchos hilos en ejecución. Es posible que varios de ellos deseen modificar el mismo objeto al mismo tiempo; en este caso es necesario sincronizarlos. Por ejemplo, en una aplicación por internet de reservas de entradas para una ópera, dos hilos tratan de reservar simultáneamente el palco 5; se puede producir una situación no deseada, a los dos hilos se les está ofreciendo como libre el palco, si no se sincronizan los dos hilos se llevarían dicho palco. Otra situación que hace necesaria la sincronización es cuando un hilo debe esperar datos proporcionados por otro hilo.

Para solucionar este tipo de problemas se sincronizan los procesos. Los bloques de código que acceden a un mismo recurso (un objeto, un archivo, . . .) desde dos o más hilos constituyen secciones críticas. Para marcar secciones críticas se utiliza el modificador synchronized. Por ejemplo:

        public synchronized void asignarSeat(int n, char s){
                   while(condición){
                               sentencias;
                   }
        }

De esta forma, durante el tiempo que un hilo ejecuta el método (o bloque de código) sincronizado, el recurso (objeto) queda bloqueado (en lock). Otros hilos que quieran acceder al mismo recurso se quedan a la espera de una notificación (notify()) de que ha terminado el bloqueo.

Ejemplo

Se diseña una clase de gestión de datos. Un método pone los datos en un buffer (arreglo), otro método retira datos del buffer. Los dos métodos se declaran sincronizados. La clase GestorDatos dispone de un arreglo que se crea en el constructor con un tamaño determinado por un constante (MX). El método ingresarDato() asigna valores arbitrarios al buffer, el método retirarDato() extrae elementos del buffer. Estos dos métodos deben declararse sincronizados.

        public class GestorDatos {
                   final static int MX=50;
                   private int datos[];
                   private int ultimo;

                 public GestorDatos(){
                            this.datos = new int[MX];
                            ultimo = -1;
                 }
                 public synchronized void ingresarDato(int dato){
                            while(ultimo<datos.length-1){
                                        ++ultimo;
                                        datos[ultimo] = dato+2*ultimo+1;
                            }
                 }
                 public synchronized void retirarDato(){
                            int datoRetirado;
                            while(ultimo>=0){
                                        datoRetirado = datos[ultimo];
                                        ––ultimo;
                                        System.out.println(«Retirado: «+ datoRetirado);
                            }
                 }
        }

Ahora suponga que la aplicación dispusiera de dos hilos, uno que se encargue de ingresar datos y el otro de retirar datos:

        hiloGenera.start();  {desde run() llama a ingresarDato()}
        hiloRetira.start();  {desde run() llama a retirarDato()}

El primer hilo bloquea el objeto Dato, el otro hilo quiere acceder al mismo objeto por lo cual se queda a la espera de que quede libre («sin lock») el objeto para poder trabajar con él.

Dentro de un método sincronizado no se debe utilizar el método sleep() debido a que el objeto se va a quedar bloqueado durante el tiempo de sleep. Se recomienda utilizar el método wait() de la clase Object. El método wait() detiene el hilo durante los milisegundos del argumento y lo pone en cola de espera, pero deja de bloquear al objeto. El hilo deja la cola de espera cuando acaba el tiempo, o hasta que otro hilo active el método notify() (o notifyAll()) de ese mismo objeto. La declaración de estos métodos se encuentra en la clase Object:

         public final void wait()
         public final void wait(long mensaje)
         public final void notify()
         public final void notifyAll()