1.2 Hilos en el ambiente de programación de Java
El ambiente de desarrollo de Java soporta programación con múltiples
hilos por medio de bibliotecas, el lenguaje mismo y con la ayuda
del sistema de tiempo de ejecución. A continuación se listan las
características más importantes con las que cuenta Java para soportar
el uso de hilos.
- El método run.
- Antes que nada, necesitamos proveer a cada
hilo con un método run para indicarle qué debe hacer. El
código de este método implementa el comportamiento en ejecución del
hilo y puede hacer, prácticamente cualquier cosa capaz de ser
codificada en Java.
Existen dos técnicas para proveer un método run para un
hilo:
- Haciendo una subclase de Thread y sobrecargando
run
- Implementando la interface Runnable. Aquí cabe
mencionar, que el autor del libro de texto (Jim Farley) incurre en
un error al mencionar que Java provee dos clases para
soportar hilos (página 84, segundo párrafo); ya que
Thread es una clase, pero Runnable es una
interface.
Tenemos buenas razones para utilizar cada una de estas técnicas. Sin
embargo, a manera de regla empírica: ``Si las clases que
elaboras deben ser subclases de otras clases (un caso común es
Applet), entonces debe usarse Runnable.''
- El ciclo de vida de un hilo.
- Una vez que el hilo hace algo,
podemos manejar el ciclo de vida de un hilo: como crear y
arrancar un hilo, algunas cosas especiales que podemos hacer
mientras se ejecuta y como detenerlo.
En el siguiente diagrama podemos ver los estados en los que un
hilo puede estar durante su vida.
Figure:
Ciclo de vida de un hilo de Java, (esta gráfica es
cortesía de The Java Tutorial [5]).
|
|
- Prioridades.
- La gran ventaja que ofrecen los hilos (como se
puede inferir de lo anteriormente expuesto) es que corren de manera
concurrente. Conceptualmente, esto es cierto, pero en la práctica
generalmente no es posible. La mayoría de las computadoras tienen un
sólo procesador, por lo tanto los hilos corren uno a la vez de
forma tal que proveen la ilusión de concurrencia (esto es llamado
scheduling). El sistema de ejecución de Java soporta un
algoritmo determinístico (para el scheduler) llamado fixed
priority scheduling. Este algoritmo asigna tiempo de ejecución a
un hilo basado en su prioridad relativa a los demás
hilos que están listos para ejecutarse.
Cuando se crea un nuevo hilo, hereda su prioridad del hilo que
lo crea, ésta puede ser modificada con el método
setPriority. Las prioridades son enteros entre
MIN_PRIORITY y MAX_PRIORITY (constantes definidas
en la clase Thread). Entre más alto el entero, más alta la
prioridad. Si dos hilos con la misma prioridad están
esperando que el CPU los ejecute, el scheduler escoge uno utilizando
round-robin (i.e. escoge uno aleatoriamente, se supone que
round-robin ofrece iguales probabilidades de ejecución a los hilos
en cuestión). El hilo escogido para ejecución, corre hasta que
alguna de estas condiciones sea verdadera:
- Un hilo con mayor prioridad está listo para ejecución.
- El hilo cede su lugar (yields), o su método run
termina.
- En sistemas que soportan rebanadas de tiempo (time slicing),
su tiempo asignado ha expirado.
En ese momento el segundo hilo es atendido por el CPU y así
sucesivamente hasta que el intérprete termina.
El algoritmo del scheduler es preemptive. Si en cualquier
momento un thred con mauyor prioridad que los demás está listo para
ser ejecutado, el sistema de ejecución de Java lo escoge para ser
ejecutado. Entonces decimos que el nuevo hilo (de mayor prioridad)
desaloja (domina, toma la primera posición, etc.) a los demás.
La prioridad de los hilos debe ser utilizada sólo para modificar
la política de asignación del scheduler (por cuestiones de
eficiencia) y no para manejar la correctez de un
programa. Esto debido a que el scheduler puede decidir ejecutar a un
hilo con menor prioridad para evitar inanición.
- Sincronización.
- Cuando tenemos varios hilos, muchas veces
deseamos que éstos pueden compartir datos, por lo tanto, es
indispensable saber sincronizar sus actividades; ésto también nos
permitirá evitar inanición y abrazos mortales.
En muchas situaciones, hilos que se ejecutan concurrentemente
comparten información y deben considerar el estado de las
actividades de los demás hilos. Un modelo muy común de tales
conjuntos de hilos es conocido como escenarios de
productor/consumidor, donde el productor genera un flujo de datos
que es consumido por un consumidor.
Los segmentos de código dentro de un programa que accesan el mismo
objeto desde hilos separados (concurrentes) se llaman
regiones críticas. En Java, una sección crítica puede ser
un bloque o un método (el autor del libro de texto (pp. 92, párrafo
5), menciona que el método o bloque de código es sincronizado
sobre una clase, un objeto o arreglo, lo cual es cierto;
sin embargo durante la primera exposición hubo gran discusión en la
clase, porque no quedaba claro como sincronizar un objeto por
ejemplo. La respuesta es clara ahora: ¡no hay tal cosa como
sincronizar un objeto!, sólo bloques de código o métodos pueden ser
sincronizados y -como efecto lateral- una clase, un objeto o un
arreglo adquieren un candado -lock. Éste candado lo asigna Java a
todo objeto que tenga código sincronizado y es utilizado para
sincronizar a todos los hilos que utilicen ese ``código''
y evitar así situaciones de concurso. La discusión en clase fue
provocada por un mal entendido.)
Java provee algunos métodos más para coordinar las actividades de
los hilos, como notifyAll, wait, etc., pero
algunos sólo son mencionados en el texto (¿Debido a los ejemplos de
juguete -no distribuídos, por cierto?) y su función es evidente,
por lo cual támpoco entraremos en detalle aquí.
- Agrupamientos de hilos.
- Este tema sólo recibe dos párrafos en
el capítulo. De hecho, el autor da a entender que podemos -si
queremos- agrupar hilos para formar una jerarquía, quedando
implícito el hecho ``si queremos''. La realidad no es así, todo
hilo en Java es miembro de un grupo. Un grupo de hilos nos da
las herramientas para meter varios hilos en un sólo objeto y
manipularlos (a todos) al mismo tiempo (a esto le llama:
``comodidad''). La clase ThreadGroup implementa los grupos
de hilos en Java.
El sistema de ejecución de Java pone a un hilo en un grupo durante
la construcción del hilo. Cuando se crea un hilo, uno puede
escoger el grupo al que pertenecerá o permitir que el sistema
seleccione un grupo razonable por omisión para nuestro nuevo
hilo. El hilo así creado es miembro permanente del grupo al cual
se una durante su creación -no puede ser cambiado.
Si se crea un hilo sin especificar su grupo en el constructor, el
sistema de ejecución automáticamente pone el nuevo hilo en el
mismo grupo que el hilo que lo crea. Cuando una aplicación de Java
arranca, el sistema de ejecución de Java crea un ThreadGroup llamado
main. A menos que se especifique lo contrario, todos los
nuevos hilos que se creen durante el desarrollo de la aplicación
serán miembros del grupo de hilos main.
La clase Thread provee tres constructores que te permiten
asignar a un grupo al hilo que estás creando. Finalmente la clase
ThreadGroup provee un conjunto de métodos que te permiten
obtener información como: qué otros hilos pertenecen al mismo
grupo, modificar los hilos por grupo: suspenderlos, activarlos,
detenerlos, etc. todo con una sóla invocación a los métodos
respectivos.
A manera de ejemplo consídere:
ThreadGroup migrupo = new ThreadGroup(``Mi grupo
de hilos'');
Thread myhilo = new Thread(migrupo, ``Un hilo
en mi grupo'');
...
elgrupo = myhilo.getThreadGroup();
...
Con esto concluimos el resumen del soporte para hilos de Java y el
resumen del capítulo 4 del libro. En la siguiente sección hablaremos
acerca de extensiones que se han propuesto para que Java ofrezca
protección de recursos compartidos y detección de abrazos mortales, dos de
los problemas más comunes que se presentan en programas
paralelos. Estos temas no fueron tocados ni en el libro ni en las
exposiciones; se presentan aquí, en un esfuerzo por complementar el
manejo de hilos, ya que si bien es bueno que el lenguaje ofrezca
soporte para ellos de manera nativa; en lo visto en el seminario no se
atacó su vital importancia en el desarrollo de programas paralelos y
aplicaciones distribuidas, nuestra meta principal en este
seminario. Trataremos de resaltar algunos de los detalles más
importantes que surgen cuando queremos hacer programas paralelos y de
contestar a las preguntas: ¿Es adecuado el soporte de hilos en Java
para este tipo de sistemas? ¿puede ser mejorado?
No hay comentarios:
Publicar un comentario