Multitarea

5.359 visualizaciones

Publicado el

Publicado en: Tecnología
0 comentarios
1 recomendación
Estadísticas
Notas
  • Sé el primero en comentar

Sin descargas
Visualizaciones
Visualizaciones totales
5.359
En SlideShare
0
De insertados
0
Número de insertados
51
Acciones
Compartido
0
Descargas
88
Comentarios
0
Recomendaciones
1
Insertados 0
No insertados

No hay notas en la diapositiva.
  • Objetivos Entender el concepto de multihilado. Apreciar la forma en que el multihilado puede mejorar el rendimiento. Entender el ciclo de vida de un hilo. Conocer la creación, iniciación, pausa y detención de un thread. Entender la prioridades y la planificación de hilos. Entender los hilos daemon y los grupos de hilos. Comprender el uso de métodos sincronizados. Comprender el uso de bloques sincronizados. Conocer las utilidades de concurrencia añadidas en Tiger
  • Multitarea vs. Multiproceso Un concepto fundamental en programación es la idea de manejar mas de una tarea a la vez. Muchos problemas de programación requieren que el programa pueda detener lo que esté haciendo, tratar con algún otro problema y regresar al proceso principal. La solución se ha aproximado de muchas maneras. Inicialmente, programadores con conocimiento de lenguajes de máquina de bajo nivel utilizaron servicios de interrupción, tal que la suspensión del proceso principal se hacia a través de una interrupción del hardware. Esta solución era difícil y no portátil, porque se debía preparar un programa por cada tipo de máquina. A veces las interrupciones son necesarias para manejar tareas de tiempo crítico, pero existen problemas en los es posible dividir en partes. Dentro de un programa, estas partes que corren separadamente se llaman hilos (Threads) y el concepto general se llama Multithreading (Multitarea) . Un ejemplo común de el multithreading es la interface creada por el usuario. Usando hilos, un usuario puede apretar un botón y puede conseguir una contestación rápida en lugar de esperar hasta que el programa termina su tarea actual . Ordinariamente, los hilos son una manera de asignar el tiempo de un solo procesador. Pero si el sistema operativo apoya procesadores múltiples, cada hilo puede asignarse a un procesador diferente y ellos pueden correr realmente en paralelo. Una de las características convenientes de Multithreading es que el programador no se preocupa sobre si existe uno o más procesadores. El programa es lógicamente dividido en hilos y si la máquina tiene más de un procesador entonces el programa corre más rápidamente, sin necesidad de ajuste especial.
  • Pero existe una dificultad para los recursos compartidos. Si usted tiene más de un hilo que ejecuta, y está esperando acceder al mismo recurso entonces usted tiene un problema. Por ejemplo, dos procesos no pueden enviar información simultáneamente a una impresora. Para resolver el problema, deben proteger los recursos que puedan compartirse, como la impresora, mientras se utilicen estos recursos. Para que un hilo proteja un recurso, debe completar su tarea y liberar el recurso para que alguien más pueda usarlo. El método Threading se apoya a nivel del objeto, para que un hilo de ejecución sea representado a través de un objeto. Java también proporciona el recurso de protección. Puede cerrar con llave la memoria de cualquier objeto (qué es, después de todos, un tipo de recurso compartido) para que sólo un hilo pueda usarlo en un momento dado. Esto se logra utilizando la sentencia synchronized . Otros tipos de recursos deben ser protegidos explícitamente por el programador, típicamente creando un objeto para representar el bloqueo que todos los hilos deben verificar, antes de acceder al recurso. Multiprocessing significa la existencia de múltiples procesadores, que se les asigna un proceso y que corren de manera simultánea. En realidad, Multithreading simula Multiprocessing, asignándole un tiempo mínimo a cada proceso para su ejecución.
  • Programación de Threads (hilos) Los threads (hilos o hebras) son una aparición relativamente reciente en Informática, pero muy útiles. La idea fundamental es bien sencilla. En la programación tradicional hay un solo flujo de control, motivado fundamentalmente porque la máquina internamente suele tener un solo procesador (una sola "mente" que realiza las instrucciones, una tras otra). Hay miles de ejemplos en los que puede ser útil pensar en varios flujos de ejecución (threads): la posibilidad de editar mientras seguimos cargando o salvando un gran fichero, la posibilidad de visualizar una página mientras se están buscando las siguientes, la visualización de varios procesos que ocurren a la vez de forma independiente, etc. Realmente no deberíamos llamar a esto multiprocesamiento sino multisubprocesamiento, porque lo que ocurre es que dentro del mismo proceso (compartiendo el mismo entorno volátil) surgen varios hilos de ejecución. El S.O. irá alternando entre procesos y además una vez que llegue a nuestro proceso alternará a su vez sobre cada uno de los hilos, creándose así multisubproceso. Esto tiene la ventaja de un menor tiempo de conmutación en comparación con el alternamiento clásico de procesos en un S.O.
  • Estado de un Thread (hilo) Un thread tras su creación pasa a estado LISTO de ahí puede pasar a EN EJECUCIÓN o no, esto dependerá del Dispatcher. Una vez EN EJECUCIÓN procesará su código hasta que se le acabe el quantum asignado por el S.O. o bien hasta que termine (si le da tiempo), o también se puede dar el caso de que alguien lo elimine mediante stop(). Los procesos pasaran a ejecución dependiendo de sus prioridades y haciendo un round robin. Una vez EN EJECUCIÓN los threads pueden, o pasar a LISTO de nuevo (si se acaba el quantum) o pasar a otros estados, a saber: EN ESPERA, DORMIDO, SUSPENDIDO y BLOQUEADO. Esto dependerá de la ejecución de ciertos métodos sobre el Thread (o la ocurrencia de ciertos sucesos). Un Thread pasará a DORMIDO cuando se invoque el método sleep(), permanecerá así (sin consumir recursos) hasta que se le acabe el tiempo de "siesta", momento en el que volverá a LISTO. En este estado, el thread no consume recursos, es decir, no es candidato a serle asignado el procesador hasta que no despierte. El Thread pasará a BLOQUEADO cuando tenga que sufrir una espera debida a E/S, saldrá de este estado en cuanto termine la E/S (tampoco consume recursos mientras espera). El estado SUSPENDIDO es para aquellos Threads que han sufrido la invocación del método suspend(), en este estado permanecerán hasta que alguien los llame mediante resume(). Por supuesto tampoco consumen recursos en este estado. Y por último el Thread pasará a EN ESPERA cuando alguien invoque un wait(), entonces el Thread pasará a esperar en una cola. Esto implica que la próxima vez que se ejecute un notify(), el Thread que se despertará será el primero que entró. También podemos ejecutar notifyAll(), de forma que sacamos a todos de la cola.
  • Resumiendo, un thread entra en el estado "No Ejecutable" cuando ocurre uno de estos cuatro eventos: Alguien llama a su método sleep(). Alguien llama a su método suspend(). El thread utiliza su método wait() para esperar una condición variable. El thread está bloqueado durante la I/O. Esta lista indica la ruta de escape para cada entrada en el estado "No Ejecutable": Si se ha puesto a dormir un thread, deben pasar el número de milisegundos especificados. Si se ha suspendido un thread, alguien debe llamar a su método resume(). Si un thread está esperando una condición variable, siempre que el objeto propietario de la variable renuncie mediante notify() o notifyAll(). Si un thread está bloqueado durante la I/O, cuando se complete la I/O. Un thread puede morir de dos formas: por causas naturales o siendo asesinado (parado). Una muerte natural se produce cuando su método run() sale normalmente. Se puede matar un thread en cualquier momento llamando a su método stop(). El método stop() provoca una terminación súbita del método run() del thread. Si el método run() estuviera realizando cálculos sensibles, stop() podría dejar el programa en un estado inconsistente. Normalmente, no se debería llamar al método stop().
  • La clase Thread En Java existen dos tipos de Threads: la mayor parte son hilos del usuario, tienen una interfaz de usuario y existen para servir a esta interfaz. Algunos hilos solamente se ejecutan en background. Se denominan Threads demonio. Llamando a setDaemon() se puede especificar que un Thread es un demonio. La clase Thread forma parte del paquete java.lang y provee una implementación de threads independiente del sistema. La clase Thread provee el comportamiento genérico de los threads: arranque, ejecución, interrupción, asignación de prioridades, etc.. El método run() implementa la funcionalidad del thread. El método run() de default, provisto por la clase Thread no hace nada. JAVA está basado en threads y por ende siempre hay un thread ejecutándose junto con las aplicaciones de los usuarios, por ejemplo el garbage collector es un thread que se ejecuta en background, las GUI´s recolectan los eventos generados por el usuario en threads separados, etc..
  • La Interface Runnable Normalmente se usará la opción de Runnable cuando la clase que va a contener la lógica del thread ya herede de otra clase (Swing, Applets,...). Simplemente fuerza la implementación del método run(). Existe para paliar la falta de herencia múltiple en el lenguaje Java.
  • Crear un Thread Los hilos interactúan con el ambiente “run-time” de Java respondiendo a los eventos que ocurren. Cuando se crea una clase derivada de la clase Thread o que implementa la interfaz Runnable , se especifican métodos que determinan cómo el programa responde a estos eventos. Java siempre crea por lo menos un hilo de ejecución. Efectivamente, para aplicaciones con el método main, la máquina virtual de Java crea un hilo. Constructores básicos de la Clase Thread Thread(): Crea un thread con nombre "Thread-" + N (siendo n un número secuencial). Thread( String name ): Crea un thread con el nombre indicado como parámetro. Thread( Runnable target ): Crea un thread asociado al objeto target. El nombre es "Thread-" + N. Thread( Runnable target, String name ): Como el anterior, pero asignándole el nombre que se pasa como parámetro.
  • Iniciar un thread La forma más sencilla de crear una hebra en Java es diseñar una subclase de java.lang.Thread. En la subclase redefiniremos el método run(), que viene a ser algo así como el main() de una hebra. Para comenzar la ejecución paralela de la nueva hebra, usaremos su método start(). El orden en que se realizan las operaciones de las distintas hebras puede cambiar de una ejecución a otra (en función de cómo se asigne la CPU a la distintas hebras de nuestro programa).
  • Una hebra extendiendo a Thread
  • Una hebra implementando la interface Runnable
  • Prioridades En las configuraciones de computadoras en las que se dispone de una única CPU, los threads se ejecutarán de a uno a la vez simulando concurrencia. Se denomina scheduling la estrategia que determina el orden de ejecución de múltiples threads sobre una única CPU. El sistema de ejecución de JAVA soporta un algoritmo de scheduling simple y determinístico llamado scheduling de prioridad fija, que consiste en determinar el orden en que se ejecutarán los threads de acuerdo a la prioridad que ellos tienen. Cuando se crea un thread, éste hereda la prioridad del thread que lo creo (NORM_PRIORITY). Es posible modificar la prioridad de un thread, después de su creación usando el método setPriority. Las prioridades de los threads son números enteros que varían entre las constantes MIN_PRIORITY y MAX_PRIORITY (definidas en la clase Thread). En un momento dado, 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 prioridad inferior.
  • Ejemplo de asignación de prioridad
  • Grupo de Threads Cada thread de Java es miembro de un grupo de threads. Los grupos proporcionan un mecanismo para la colección de varios threads dentro de un sólo objeto y la manipulación de esos threads de una vez, mejor que de forma individual. Por ejemplo, se puede arrancar o suspender todos los threads de un grupo con una sola llamada a un método. Los grupos de threads de Java están implementados por la clase ThreadGroup del paquete java.lang. El sistema de ejecución pone un thread dentro de un grupo durante su construcción. Cuando se crea un thread, también se puede permitir que el sistema de ejecución ponga el nuevo thread en algún grupo por defecto razonable o se puede especificar explícitamente el grupo del nuevo thread. El thread es un miembro permanente del grupo al que se unió durante su creación - no se puede mover un thread a otro grupo después de haber sido creado. Si se crea un nuevo Thread sin especificar su grupo en el constructor, el sistema de ejecución automáticamente sitúa el nuevo thread en el mismo grupo que del thread que lo creó (conocido como el grupo de threads actual y el thread actual, respectivamente). Cuando se arranca por primera vez una aplicación Java, el sistema de ejecución crea un ThreadGroup llamado "main". Entonces, a menos que se especifique otra cosa, todos los nuevos threads que se creen se convierten en miembros del grupo de threads "main".
  • Creación de Grupos Constructores para asociar una tarea con un grupo Thread(ThreadGroup group, String name): Crea un thread dentro de un grupo de threads que se pasa como primer parámetro, dándole un nombre como segundo parámetro. Thread( ThreadGroup group, Runnable target ): Como el anterior, pero asociándole un objeto Runnable. Thread( ThreadGroup group, Runnable target, int stackSize ): Como el anterior, pero establece un valor en bytes para la pila de asignación en memoria. Thread(ThreadGroup group, Runnable target, String name): Como los anteriores, pasando todos los parámetros posibles: el grupo de hilos al que pertenece, el objeto Runnable asociado y el nombre del Thread.
  • Sincronización de Threads Normalmente una hebra en ejecución no tiene en cuenta lo que las otras hebras de la aplicación están haciendo. Esto es debido a que las hebras son independientes, contienen todos los datos y métodos que necesitan para su ejecución y no necesitan ningún recurso ni método exterior. En un sistema multitarea en el que se encuentren ejecutándose varias hebras simultáneamente, la sincronización pretende coordinar sus actividades para que puedan trabajar juntas y sin colisionar. Java incluye unas estructuras para la sincronización de las actividades de las hebras, que están basadas en el concepto de monitores . Un monitor es esencialmente una estructura de bloqueo asociada a un recurso, que va a permitir que las hebras accedan a él en "orden". Sólo una hebra podrá acceder al recurso en un momento dado: si otra hebra intenta acceder al mismo, tendrá que esperar hasta que éste se libere. Esto se consigue utilizando métodos sincronizados . Para ello, declararemos dichos métodos como synchronized; un método sincronizado se ejecutará por completo en una determinada hebra antes de que otra empiece a ejecutarlo.
  • Ejemplo Tenemos una cuenta bancaria en la cual se le realizan ingresos desde distintas sucursales al mismo tiempo. Para que el saldo en cada momento sea el correcto, hay que restringir el acceso al saldo actual a una única hebra: si no fuera así, podría darse el caso de perder alguna anotación en la cuenta. Puede haber problemas si varias hebras quisieran incrementar el saldo al mismo tiempo y obtuvieran el mismo valor de la propiedad saldo. Ello daría lugar a que únicamente se vería reflejado el último ingreso que modificara la propiedad saldo. Declarando los métodos como sincronizados estamos eliminando esta posibilidad, ya que una única hebra podrá acceder a la cuenta. Si una hebra está a mitad ejecución de incrementarSaldo() y llega otra que quiere ejecutar al mismo método, esta última tendrá que esperar a que la primera acabe. De la misma forma que sincronizamos métodos, también podemos hacer que cualquier bloque de código se ejecute de forma sincronizada. Cuando esta parte de código es accedido, la hebra a de adquirir el "lock" del objeto anónimo antes de proceder (el objeto puede ser cualquier objeto (una variable, colección, etc.)).
  • Bloqueo de objetos Para poder bloquear un objeto e impedir que otro thread lo utilice mientras está este, se emplea la palabra synchronized en la definición de los métodos susceptibles de tener problemas de sincronización. Cuando un thread está ejecutando un método synchronized en un objeto, se establece un bloqueo en dicho objeto. Cualquier otro thread que quiera ejecutar un método marcado como synchronized en un objeto bloqueado, tendrá que esperar a que se desbloquee. Se creará una lista de espera y se irán ejecutando por orden de llegada. El objeto se desbloquea cuando el thread actual termina la ejecución del método synchronized. El sistema de bloqueo/desbloqueo es algo gestionado de forma automática por la JVM.
  • Sobre el uso de mecanismos de exclusión mutua * No hay que abusar del uso de mecanismos secciones críticas. Cada cerrojo que se usa impide potencialmente el progreso de la ejecución de otras hebras de la aplicación. Las secciones críticas eliminan el paralelismo y, con él, muchas de las ventajas que nos llevaron a utilizar hebras en primer lugar. En un caso extremo, la aplicación puede carecer por completo de paralelismo y convertirse en una aplicación secuencial pese a que utilicemos hebras en nuestra implementación. La ausencia de paralelismo no es, ni mucho menos, el peor problema que se nos puede presentar durante el desarrollo de una aplicación multihebra. El uso de mecanismos de sincronización como cerrojos o monitores puede conducir a otras situaciones poco deseables que también hemos de tener en cuenta: Interbloqueos [deadlocks] : Una hebra se bloquea cuando intenta bloquear el acceso a un objeto que ya está bloqueado. Si dicho objeto está bloqueado por una hebra que, a su vez, está bloqueada esperando que se libere un objeto bloqueado por la primera hebra, ninguna de las dos hebras avanzará. Ambas se quedarán esperando indefinidamente. El interbloqueo es consecuencia de que el orden de adquisición de los cerrojos no sea siempre el mismo y la forma más evidente de evitarlo es asegurar que los cerrojos se adquieran siempre en el mismo orden. Inanición [starvation] , cuando una hebra nunca avanza porque siempre hay otras a las que se les da preferencia. Por ejemplo, si le damos siempre prioridad a un “lector” frente a un “escritor”, el escritor nunca obtendrá el cerrojo para modificar el recurso compartido mientras haya lectores. Los escritores "se morirán de inanición". Inversión de prioridad: El planificador de CPU decide en cada momento qué hebra, de entre todas las no bloqueadas, ha de disponer de tiempo de CPU. Usualmente, esta decisión viene influida por la prioridad de las hebras. Sin embargo, al usar cerrojos se puede dar el caso de que una hebra de prioridad alta no avance nunca, justo lo contrario de lo que su prioridad nos podría hacer pensar. Imaginemos que tenemos tres hebras H1, H2 y H3, de mayor a menor prioridad. H3 está ejecutándose y bloquea el acceso al objeto O. H2 adquiere la CPU al tener más prioridad que H3 y comienza un cálculo muy largo. Ahora, H1 le quita la CPU a H2 y, al intentar adquirir el cerrojo de O, queda bloqueada. Entonces, H2 pasa a disponer de la CPU para proseguir su largo cómputo. Mientras, H1 queda bloqueada porque H3 no llega a liberar el cerrojo de O. Mientras que H3 no disponga de algo de tiempo de CPU, H1 no avanzará y H2, pese a tener menor prioridad que H1, sí que lo hará. El planificador de CPU puede solucionar el problema si todas las hebras disponibles avanzan en su ejecución, aunque sea más lentamente cuando tienen menor prioridad. De ahí la importancia de darle una prioridad alta a las hebras cuyo tiempo de respuesta haya de ser bajo, y una prioridad baja a las hebras que realicen tareas muy largas. Todos estos problemas ponen de manifiesto la dificultad que supone trabajar con aplicaciones multihebra. Hay que asegurarse de asignar los recursos cuidadosamente para minimizar las restricciones de acceso en exclusiva a recursos compartidos. En muchas ocasiones, por ejemplo, se puede simplificar notablemente la implementación de las aplicaciones multihebra si hacemos que cada una de las hebras trabaje de forma independiente, incluso con copias distintas de los mismos datos si hace falta. Sin datos comunes, los mecanismos de sincronización se hacen innecesarios. * Fernando Berzal, Concurrencia , http://elvex.ugr.es/decsai/java/index.html
  • Utilidades de Concurrencia en Tiger En Tiger se han sido agregados utilidades para el manejo de concurrencia que responden al JSR-166, con las siguientes características: Permite el desarrollo simple de poderosas aplicaciones multitarea. Colecciones que proporcionan una estructura de datos rica que manejan esta capacidad. Funcionamiento Beat C en usos de servidor de alta capacidad. Proporciona componentes básicos de concurrencia y bloqueo más ricos. wait(),notify() y synchronized son primitivos. Mejora la adaptabilidad, funcionamiento, legibilidad y seguridad de hilo de aplicaciones basadas en Java. Como resultado se consigue: Reducir el esfuerzo en la programación. Incrementar el rendimiento. Incrementar la legibilidad. Elimine peligros de interbloqueos, inanición, inversión de prioridad, o la conmutación de contexto excesiva. Capacidad de mantenimiento mejorada. Productividad aumentada.
  • Framework para la programación de tareas (hilos) La interface Executor Proporciona un camino de sumisión de tarea que desacopla de la ejecución. Ejecución: mecanismo de como cada tarea será controlada, incluyendo los detalles de empleo de hilo y programación. Muchas implementaciones de Executor imponen algún tipo de la limitación a como y cuando las tareas son programadas. Executor y ExecutorService ExecutorService añade administración del ciclo de vida de un ejecutor
  • Creación de ExecutorService mediante Executors Executors permite crear diferentes implementaciones de ExecutorService Ejemplo : podríamos forzar a que sólo se ejecute a la vez 3 tareas, tomando como referencia los ejemplos de las págs. 14-11 y 14-12 (sólo redefiniremos el main): Se muestra parte de la ejecución de las tareas, observamos que luego de terminar las tres primeras se ejecutan las dos siguientes:
  • Soporte para invocables y soporte asíncrono El problema : si un nuevo hilo es comenzado en un uso, no hay actualmente ningún modo de devolver un resultado de aquel hilo al hilo que lo comenzó sin el empleo de una variable compartida y la sincronización apropiada. Esto es complejo y hace el código más difícil para entender y mantener. La solución : implementar la interface Callable y delegar su ejecución a un Executor, éste creará un objeto que implemente la interface Future, el cual gestionará la ejecución y respuesta asíncrona de la tarea mientras se realizan tareas paralelas. La interface Future
  • Ejemplo de Callable: Ejecución con Executor y Future:
  • Sincronizadores (Semáforos)
  • Colecciones concurrentes El productor produce un mensaje cada segundo y lo deposita en una cola de bloqueo:
  • Un consumidor trata a cada momento de consumir el mensaje, se bloquea esperando que el exista mensajes en la cola de bloqueo: Creamos un Productor y 2 Consumidores, el comportamiento dependerá de la capacidad de la cola y la velocidad de producción del Productor:
  • Variables atómicas Ejemplo : Asegurando la fiabilidad de lecturas e ingresos con variables atómicas.
  • Cerraduras
  • Práctica 14: Multitarea Objetivos Hacer uso de la capacidad de ejecución de múltiples hilos de Java Hacer uso de las utilidades de concurrencia de Tigger Ejercicios Tomando como referencia el caso del tanque de la práctica 8 (pag. 8-26), haciendo uso de multitarea implementar la simulación de llenado y desagüe del tanque.¿Cuantas tareas necesitaría? Simular la una hora de atención a los clientes de un banco, si se sabe que existen 5 terminales y que el tiempo de llegada de un cliente es Math.log(Math.random()*100) segundos y demora en ser atendido Math.log(Math.random()*100) segundos. Mostrar el número de usuarios en cola, y el tiempo promedio de espera de los clientes. ¿Cuántos terminales se necesitaría para que un cliente sea atendido inmediatamente?. Determine en que puntos debería utilizarse la multitarea en el caso de Gestión Académica, cómo la implementaría con las utilidades de Tigger. Determine en que puntos debería utilizarse la multitarea en el caso del Banco, cómo la implementaría con las utilidades de Tigger. Determine en que puntos debería utilizarse la multitarea en el caso de la Empresa XYZ, cómo la implementaría con las utilidades de Tigger.
  • Multitarea

    1. 1. Multitarea Ing. Rolando Steep Quezada Martínez [email_address] Programación Avanzada en Java
    2. 2. Multitarea vs. Multiproceso <ul><li>Multiproceso significa que el equipo hardware cuenta con mas de un procesador (CPU) y por tanto ejecuta varias tareas a la vez. </li></ul><ul><li>Multitarea significa que varias tareas comparten el único procesador (CPU) dándonos la sensación de multiproceso. </li></ul><ul><li>La multitarea se consigue mediante un planificador de tareas que va dando slots de CPU a cada tarea. </li></ul>
    3. 4. Programación de Threads (hilos o hebras) <ul><li>La programación multithreading (multienhebrada o multi-hilo) permite la ocurrencia simultánea de varios flujos de control. </li></ul><ul><li>Cada uno de ellos puede programarse independientemente y realizar un trabajo, distinto, idéntico o complementario, a otros flujos paralelos. </li></ul>
    4. 5. Estados de un Thread (hilo)
    5. 7. La clase Thread <ul><li>La clase principal es java.lang.Thread. </li></ul><ul><li>Nos ofrece el API genérico de los threads así como la implementación de su comportamiento. </li></ul><ul><li>La lógica que va a ejecutar un thread se introduce en el método: </li></ul><ul><li> public void run() </li></ul><ul><li>Cuando termina la ejecución del método run() se termina el thread. </li></ul><ul><li>La clase java.lang.Thread contiene un método run() vacío. </li></ul>
    6. 8. La Interface Runnable <ul><li>El interface Runnable solamente declara una función miembro denominada run , que han de definir las clases que implementen este interface. </li></ul><ul><li>Como Java sólo soporta herencia simple y puede que el objeto que representa nuestra hebra sea mejor implementarlo como subclase de otra clase de nuestro sistema, Java nos permite crear una hebra a partir de cualquier objeto que implemente la interfaz Runnable. </li></ul>
    7. 9. Crear un Thread <ul><li>Para crear un thread hay que instanciarlo llamando al constructor como con el resto de clases Java. </li></ul><ul><li>Dependiendo de cómo hayamos implementado el thread se actuará de una forma u otra: </li></ul><ul><ul><li>Si hereda de la clase java.lang.Thread, simplemente se instancia nuestra clase. </li></ul></ul><ul><ul><li>Si implementa el interfaz java.lang.Runnable, se instancia la clase java.lang.Thread pasándole como parámetro del un constructor una instancia de nuestra clase. </li></ul></ul>
    8. 10. Iniciar un thread <ul><li>Se usan dos técnicas para proveer el método runa un thread: </li></ul><ul><ul><li>Extender la clase Thread y sobre escribir el método run(). </li></ul></ul><ul><ul><li>Implementar la interface Runnable. En este caso, es un objeto Runnable el que provee el método run() del thread. </li></ul></ul>
    9. 13. Prioridades <ul><li>Cuando existe un único procesador (CPU) no existe multiproceso real. Los distintos threads van compartiendo dicho procesador (CPU) siguiendo las políticas o algoritmos del Sistema Operativo. </li></ul><ul><li>Pero esas políticas o algoritmos pueden tener en cuenta prioridades cuando realiza sus cálculos. </li></ul><ul><li>La prioridad de un thread se establece mediante el método setPriority() pasándole un int entre: </li></ul><ul><ul><li>Thread.MAX_PRIORITY </li></ul></ul><ul><ul><li>Thread.MIN_PRIORITY </li></ul></ul>
    10. 15. Grupo de Threads <ul><li>Todo thread es miembro de un grupo de threads. </li></ul><ul><li>La clase java.lang.ThreadGroup implementa los grupos de threads. </li></ul><ul><li>El grupo de threads al que pertenece un thread se establece en su construcción. Luego es inmutable. </li></ul><ul><li>Por defecto, un thread pertenece al grupo al que pertenece el thread desde donde se le creo. </li></ul><ul><li>El grupo del thread principal se llama “main”. </li></ul>
    11. 16. Creación de Grupos <ul><li>Para crear un thread en un grupo distinto al seleccionado por defecto, hay que añadir como parámetro del constructor la instancia del grupo: </li></ul><ul><ul><li>ThreadGroup tg = new ThreadGroup(“Mis threads”); </li></ul></ul><ul><ul><li>Thread t = new Thread(tg); </li></ul></ul><ul><li>Para conocer el grupo al que pertenece un thread: </li></ul><ul><ul><li>t.getThreadGroup(); </li></ul></ul>
    12. 17. Sincronización de Threads <ul><li>Hasta ahora vimos threads asincrónicos que no comparten datos ni necesitan coordinar sus actividades. </li></ul><ul><li>Hay situaciones en las que los threads concurrentes comparten datos y en donde es necesario considerar el estado y coordinar las actividades de otros threads. Estos escenarios de programación se llaman productor/consumidor, y consisten en un productor que genera streams de datos que posteriormente serán consumidos por el consumidor. </li></ul><ul><li>Los threads que comparten recursos comunes deben sincronizarse de alguna manera. </li></ul>
    13. 19. Bloqueo de Objetos <ul><li>Una solución al problema de inconsistencia de datos consiste en que, para acceder a un recurso compartido, haya antes que “adquirirlo”. </li></ul><ul><li>La hebra que adquiere el recurso bloquea el acceso a él. </li></ul><ul><li>Las demás hebras, para acceder al recurso, intentarán adquirirlo y se quedarán bloqueadas hasta que lo “libere” la hebra que lo posee. </li></ul><ul><li>De esta forma, el acceso al recurso compartido se realiza secuencialmente (por lo que deberemos tener cuidado para no eliminar completamente el paralelismo de nuestra aplicación. </li></ul>
    14. 21. Utilidades de Concurrencia en Tiger <ul><li>El paquete incorporado java.util.concurrent provee: </li></ul><ul><ul><li>Framework para la programación de tareas (hilos) </li></ul></ul><ul><ul><li>Soporte para invocables y soporte asíncrono </li></ul></ul><ul><ul><li>Sincronizadores </li></ul></ul><ul><ul><li>Colecciones concurrentes </li></ul></ul><ul><ul><li>Variables atómicas </li></ul></ul><ul><ul><li>Cerraduras </li></ul></ul><ul><ul><li>Engranaje de distribución de granularidad de nanosegundo </li></ul></ul>
    15. 22. Framework para la programación de tareas (hilos) <ul><li>El framework Executor/ExecutorService/Executors soporta: </li></ul><ul><ul><li>Invocación estandarizada. </li></ul></ul><ul><ul><li>Programación. </li></ul></ul><ul><ul><li>Ejecución. </li></ul></ul><ul><ul><li>Control de tareas asíncronas de acuerdo a políticas de ejecución establecidas. </li></ul></ul><ul><li>Executor es un interface. </li></ul><ul><li>ExecutorService es una extensión de Executor. </li></ul><ul><li>Executors es una fabrica para la creación de diversas implementaciones de ExecutorService. </li></ul>
    16. 24. Soporte para invocables y soporte asíncrono <ul><li>Una tarea invocable implementa la interface Callable, que define un el método call(), similar a run() de Runnable. </li></ul><ul><li>La llamada del hilo somete al objeto Callable al Executor y luego sigue adelante. </li></ul><ul><ul><li>Se llama a submit() y no a execute(). </li></ul></ul><ul><ul><li>Retorna u objeto de tipo Future. </li></ul></ul><ul><li>La llamada del hilo entonces recupera el resultado usando el método get() del objeto de tipo Future. </li></ul><ul><ul><li>Si el resultado está listo, es retornado, </li></ul></ul><ul><ul><li>Si el resultado no esta listo, la llamada será bloqueada </li></ul></ul>
    17. 26. Sincronizadores (Semáforos) <ul><li>Típicamente se usan para restringir el acceso a recursos de número limitado </li></ul><ul><li>El nuevo objeto Semaphore es creado con la misma cuenta que el número de recursos. </li></ul><ul><li>Una tarea tata de tener acceso a un recurso invocando el método aquire(). </li></ul><ul><li>Retorna inmediatamente el recurso si la cuenta del semáforo es mayor a cero (>0). </li></ul><ul><li>Bloquea la tarea si la cuenta es igual a cero (=0) antes que el método release() sea invocado por otra tarea. </li></ul><ul><li>aquire() y release() son operaciones atómicas de tarea segura (thread safe). </li></ul>
    18. 27. Colecciones concurrentes <ul><li>La interface BlockingQueue provee tareas seguras cuando múltiples tareas manipulan una colección. </li></ul><ul><li>ArrayBlockingQueue es una implementación concreta simple. </li></ul><ul><li>LinkedBlockingQueue, implementa la cola como una lista enlazada. </li></ul><ul><li>PriorityBlockingQueue, permite establecer prioridades entre los objetos de la cola según un Comparator. </li></ul>
    19. 29. Variables atómicas <ul><li>El paquete java.util.concurrent.atomic es añadido en Tiger </li></ul><ul><li>Es un pequeño paquete de utilidades para el soporte de tareas seguras de libre cierre sobre variables simples. </li></ul>
    20. 30. Cerraduras <ul><li>La interface Lock </li></ul><ul><ul><li>Operaciones de cierre (lock) más extensas que pueden ser obtenidas usando métodos sincronizados y sentencias. </li></ul></ul><ul><ul><li>La apertura (unlock) no es automático, se debe usar try/fynally para la apertura </li></ul></ul><ul><ul><li>El acceso no es bloqueado si se usa tryLock() </li></ul></ul><ul><li>La clase ReentrantReadWriteLock </li></ul><ul><ul><li>Implementación concreta de Lock </li></ul></ul><ul><ul><li>La tareas pueden llamar a lock() múltiples veces y no bloquear. </li></ul></ul><ul><ul><li>Útil para código recursivo </li></ul></ul>
    21. 31. Resumen <ul><li>El método run() de un Thread o Runnable contiene el código que controla la ejecución del hilo. </li></ul><ul><li>Un programa pone en marcha la ejecución de un hilo invocando el método start() del hilo que, a su vez, invoca al método run(). </li></ul><ul><li>El método isAlive() devuelve true si ya se invocó start() pero no se ha invocado stop(). </li></ul><ul><li>Cuando se invoca el método sleep() de un hilo en ejecución, ese hilo pasa al estado dormido. Un hilo dormido queda listo una vez transcurrido el tiempo de sueño especificado. Un hilo dormido no puede usar un procesador aunque haya uno disponible. </li></ul><ul><li>Tiger simplifica la programación y mantenimiento de aplicaciones que soportan multitarea. </li></ul>
    22. 32. Práctica 14 <ul><ul><li>Implementar aplicaciones que soporten multitarea </li></ul></ul><ul><ul><li>Hacer uso de las utilidades de concurrencia de Tiger </li></ul></ul>

    ×