Threads de Control

Anteriormente en esta lecci�n , hemos reclamado que los applets se ejecuten de forma concurrente.

Mientras conceptualmente esto es cierto, en la pr�ctica no lo es. La mayor�a de las configuraciones de ordenadores s�lo tienen una CPU, por eso los threads realmente se ejecutan de uno en uno de forma que proporcionan una ilusi�n de concurrencia. La ejecuci�n de varios threads en una sola CPU, en algunos �rdenes, es llamada programaci�n.

El sistema de ejecuci�n de Java soporta un algoritmo de programaci�n deterministico muy sencillo conocido como programaci�n de prioridad fija.Este algoritmo programa los threads bas�ndose en su prioridad relativa a otros threads "Ejecutables".

Cuando se crea un thread Java, hereda su prioridad desde el thread que lo ha creado. Tambi�n se puede modificar la prioridad de un thread en cualquier momento despu�s de su creacci�n utilizando el m�todo setPriority(). Las prioridades de un thread son un rango de enteros entre MIN_PRIORITY y MAX_PRIORITY (constantes definidas en la clase Thread). El entero m�s alto, es la prioridad m�s alta. En un momento dado, cuando varios threads est� listos para ser ejecutados, el sistema de ejecuci�n elige aquellos thread "Ejecutables" con la prioridad m�s alta para su ejecuci�n. S�lo cuando el thread se para, abandona o se convierte en "No Ejecutable" por alguna raz�n empezar� su ejecuci�n un thread con prioridad inferior. Si dos threads con la misma prioridad est�n esperando por la CPU, el programador elige uno de ellos en una forma de competici�n. El thread elegido se ejecutar� hasta que ocurra alguna de las siguientes condiciones.

  • Un thread con prioridad superior se vuelve "Ejecutable".
  • Abandona, o su m�todo run() sale.
  • En sistemas que soportan tiempo-compartido, su tiempo ha expirado.

Luego el segundo thread tiene una oprtunidad para ejecutarse, y as� continuamente hasta que el interprete abandone.

El algoritmo de programaci�n de threads del sistema de ejecuci�n de Java tambi�n es preemptivo. Si en cualquier momento un thread con prioridad superior que todos los dem�s se vuelve "Ejecutable", el sistema elige el nuevo thread con prioridad m�s alta.

Se dice que el thread con prioridad superior prevalece sobre los otros threads.

Regla del Pulgar:

En un momento dado, el thread con prioridad superior se est� ejecutando. Sin embargo, este no es una garant�a. El programador de threads podr�a elegir otro thread con prioridad inferior para evitar el hambre. Por esta raz�n, el uso de las prioridades s�lo afecta a la politica del programador para prop�sitos de eficiencia. No dependas de la prioridad de los threads para algoritmos incorrectos.

.�La carrera de Threads

Este c�digo fuente Java implementa un applet que anima una carrera entre dos threads "corredores" con diferentes prioridades. Cuando pulses con el rat�n sobre el applet, arrancan los dos corredores. El corredor superior , llamado "2", tiene una prioridad 2.

El segundo corredor, llamado "3", tiene una prioridad 3.

Prueba esto: Pulsa sobre el applet inferior para iniciar la carrera.

Este es el m�todo run() para los dos corredores.

public int tick = 1;
public void run() {
    while (tick < 400000) {
        tick++;
    }
}

Este m�todo s�lo cuenta desde 1 hasta 400.000. La variable tick es p�blica porque la utiliza el applet para determinar cuanto ha progresado el corredor (c�mo de larga es su l�nea).

Adem�s de los dos threads corredores, el applet tiene un tercer thread que controla el dibujo.

El m�todo run() de este thread contiene un bucle infinito; durante cada iteraci�n del bucle dibuja una l�nea para cada corredor (cuya longitud se calcula mediante la variable tick), y luego duerme durante 10 milisegundos. Este thread tiene una prioridad de 4 -- superior que la de los corredores. Por eso, siempre que se despierte cada 10 milisegundos, se convierte en el thread de mayor prioridad, prevalece sobre el thread que se est� ejecutando, y dibuja las l�neas.

Se puede ver c�mo las l�neas van atravesando la p�gina.

Como puedes ver, esto no es una carrera justa porque un corredor tiene m�s prioridad que el otro.

Cada vez que el thread que dibuja abandona la CPU para irse a dormir durante 10 milisegundos, el programador elige el thread ejecutable con una prioridad superior; en este caso, siempre ser� el corredor llamado "3". Aqu� tienes otra versi�n del applet que implementa una carrera justa, esto es, los dos corredores tienen la misma prioridad y tienen las mismas posibilidades para ser elegidos.

Prueba esto: Pulsa sobre el Applet para iniciar la carrera.

En esta carrera, cada vez que el thread de dibujo abandona la CPU, hay dos threads ejecutables con igual prioridad -- los corredores -- esperando por la CPU; el programador debe elegir uno de los threads. En esta situaci�n, el programador elige el siguiente thread en una especie de competici�n deportiva.

.�Threads Egoistas

La clase Runner utilizada en las carreras anteriores realmente implementea un comportamiendo "socialmente-perjudicioso". Recuerda el m�todo run() de la clase Runner utilizado en las carreras.

public int tick = 1;
public void run() {
    while (tick < 400000) {
        tick++;
    }
}

El bucle while del m�todo run() est� en un m�todo ajustado.

Esto es, una vez que el programador elige un thread con este cuerpo de thread para su ejecuci�n, el thread nunca abandona voluntariamente el control de la CPU -- el thread se contin�a ejecutando hasta que el bucle while termina naturalmente o hasta que el thread es superado por un thread con prioridad superior.

En algunas situaciones, tener threads "egoistas" no causa ning�n problema porque prevalencen los threads con prioridad superior (como el thread del dibujo prevalece sobres los threads egoistas de los corredores. Sin embargo, en otras situaciones, los threads con m�todos run() avariciosos de CPU, como los de la clase Runner, pueden tomar posesi�n de la CPU haciendo que otros threads esperen por mucho tiempo antes de obtener una oportunidad para ejecutarse.

.�Tiempo-Compartido

En sistemas, como Windows 95, la lucha contra el comportamiento egoista de los threads tiene una estrategia conocida como tiempo-compartido. Esta estrategia entra en juego cuando existen varios threads "Ejecutables" con igual prioridad y estos threads son los que tienen una prioridad mayor de los que est�n compitiendo por la CPU. Por ejemplo, este programa Java (que est� basado en la carrera de Applets anterior) crea dos threads egoistas con la misma prioridad que tienen el siguiente �todo run().

public void run() {
    while (tick < 400000) {
        tick++;
        if ((tick % 50000) == 0) {
            System.out.println("Thread #" + num + ", tick = " + tick);
        }
    }
}

Este m�todo contiene un bucle ajustado que incrementa el entero tick y cada 50.000 ticks imprime el indentificador del thread y su contador tick.

Cuando se ejecuta el programa en un sistema con tiempo-compartido, ver�s los mensajes de los dos threads, intermitentemente uno y otro. Como esto.

Thread #1, tick = 50000
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #1, tick = 250000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 400000

Esto es porque un sistema de tiempo compartido divide la CPU en espacios de tiempo e iterativamente le da a cada thread con prioridad superior un espacio de tiempo para ejecutarse. El sistema de tiempo compartido itera a trav�s de los threads con la misma prioridad superior otorg�ndoles un peque�o espacio de tiempo para que se ejecuten, hasta que uno o m�s de estos threads finalizan, o hasta que aparezca un thread con prioridad superior. Observa que el tiempo compartido no ofrece garantias sobre la frecuencia y el orden en que se van a ejecutar los threads.

Cuando ejecutes este programa en un sistema sin tiempo compartido, sin embargo, veras que los mensajes de un thread terminan de imprimierse antes de que el otro tenga una oportunidad de mostrar un s�lo mensaje. Como esto.

Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #1, tick = 400000

Esto es porque el sistema sin tiempo compartido elige uno de los threads con igual prioridad para ejecutarlo y le permite ejecutarse hasta que abandone la CPU o hasta que aparezca un thread con prioridad superior.

Nota:

El sistema de ejecuci�n Java no implementa (y por lo tanto no garantiza) el tiempo compartido. Sin embargo, algunos sistemas en los que se puede ejecutar Java si soportan el tiempo compartido. Los programas Java no deber�an ser relativos al tiempo compartido ya que podr�an producir resultados diferentes en distintos sistemas.

Prueba esto: Compila y ejecuta las clases RaceTest y SelfishRunner en tu ordenador. �Puedes decir si su sistema tiene tiempo compartido?

Como te puedes imaginar, escribir c�digo que haga un uso intensivo de la CPU puede tener repercusiones negativas en otros threads que se ejecutan en el mismo proceso. En general, se deber�a intentar escribir threads con "buen comportamiento" que abandonen voluntariamente la CPU de forma peri�dica y le den una oportunidad a otros threads para que se ejecuten.

En particular, no escribas nunca c�digo Java que trate con tiempo compartido-- esto garantiza pr�cticamente que tu programa dar� diferentes resultados en distintos sistemas de ordenador.

Un thread puede abandonar la CPU (sin ir a dormir o alg�n otro m�todo dr�stico) con una llamada al m�todo yield(). Este m�todo da una oportunidad a otros threads con la misma prioridad. Si no existen otros threads con la misma prioridad en el estado "ejecutable", este m�todo ser� ignorado.

Prueba esto: Reescribe la clase SelfishRunner para que sea un PoliteRunner "Corredor Educado" mediante una llamada al m�todo yield() desde el m�todo run().

Asegurese de modificar el programa principal para crear PoliteRunners en vez de SelfishRunners. Compila y ejecuta las nuevas clases en tu ordenador. �No est� mejor ahora?

.�Sumario

  • La mayor�a de los ordenadores s�lo tienen una CPU, los threads deben compartir la CPU con otros threads. La ejecuci�n de varios threas en un s�lo CPU, en cualquier orden, se llama programaci�n. El sistema de ejecuci�n Java soporta un algoritmo de programaci�n determin�stico que es conocido como programaci�n de prioridad fija.
  • A cada thread Java se le da una prioridad num�rica entre MIN_PRIORITY y MAX_PRIORITY (constantes definidas en la clase Thread). En un momento dato, cuando varios threads est�n listos para ejecutarse, el thread con prioridad superior ser� el elegido para su ejecuci�n.

    S�lo cuando el thread para o se suspende por alguna raz�n, se empezar� a ejecutar un thread con priporidad inferior.

  • La programaci�n de la CPU es totalmente preemptiva. Si un thread con prioridad superior que el que se est� ejecutando actualmente necesita ejecutarse, toma inmediatamente posesi�n del control sobre la CPU.
  • El sistema de ejecuci�n de Java no hace abandonar a un thread el control de la CPU por otro thread con la misma prioridad. En otras palabras, el sistema de ejecuci�n de Java no comparte el tiempo. Sin embargo, algunos sistemas si lo soportan por lo que no se debe escribir c�digo que est� relacionado con el tiempo compartido.
  • Adem�s, un thread cualquiera, en cualquier momento, puede ceder el control de la CPU llamando al m�todo yield(). Los threads s�lo pueden 'prestar' la CPU a otros threads con la misma priorida que �l -- intentar cederle la CPU a un thread con prioridad inferior no tendr� ning�n efecto.
  • Cuando todos los threads "ejecutables" del sistema tienen la misma prioridad, el programador elige a uno de ellos en una especie de orden de competici�n.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO