1.- Introducción
Hay 2 tipos de programas según el flujo de ejecución:
- Programa de flujo único: Las actividades o tareas que lleva a cabo una a continuación de la otra, de manera secuencial.
- Programa de flujo múltiple: Coloca las actividades a realizar en diferentes flujos de ejecución.
La programación multihilo o multithreading son programas o aplicaciones de flujo múltiple.
2.- Conceptos sobre hilos
- No puede existir un hilo sin proceso.
- Un hilo no puede ejecutarse solo.
- Un proceso puede tener varios hilos.
2.1.- Recursos compartidos por los hilos
- un identificador único,
- un contador de programa propio,
- un conjunto de registros,
- una pila (variables locales).
- código,
- datos (como variables globales),
- otros recursos del sistema operativo, como los archivos abiertos y las señales.
2.2.- Ventajas y uso de hilos
- Consumen menos recursos en el lanzamiento y ejecución.
- Se tarda menos tiempo en crear.
- Cambio de contexto más rápido.
- La aplicación maneja entradas de varios dispositivos de comunicación;
- La aplicación debe poder realizar diferentes tareas a la vez;
- Interesa diferenciar tareas con una prioridad variada.
- La aplicación en un entorno multiprocesador.
3.1.- Utilidades de concurrencia del paquete java.lang
- clase Thread. Produce hilos funcionales para otras clases.
- interfaz Runnable. Añade la funcionalidad de hilo a una clase simplemente implementando la interfaz.
- clase ThreadDeath. Deriva de la clase Error, y permite manejar y notificar errores;
- clase ThreadGroup. Maneja un grupo de hilos de modo conjunto.
- clase Object. Proporciona unos cuantos métodos cruciales dentro de la arquitectura multihilo de Java. Estos métodos son wait, notify y notifyAll.
3.2.- Utilidades de concurrencia del paquete java.util.concurrent
● java.util.concurrent:
- clases de sincronización.
- interfaces para separar la lógica de la ejecución
- interfaces para gestionar colas de hilos
● java.util.concurrent.atomic. Incluye un conjunto de clases para ser usadas como variables atómicas en aplicaciones multihilo y con diferentes tipos de dato, por ejemplo AtomicInteger y AtomicLong;● java.util.concurrent.locks. Define una serie de clases como uso alternativo a la cláusula synchronized. En este paquete se encuentran algunas interfaces como por ejemplo Lock, ReadWriteLock.
4.- Creación de hilos
Para crear un hilo se usa java.lang.Thread. Se puede iniciar, detener o cancelar.
- extendiendo (heredando de) la clase thread;
- implementando la interfaz Runnable.
- extender la clase Thread es el más sencillo pero Java no permite la herencia múltiple.
- implementar Runnable es más general y flexible.
4.1.- Creación de hilos extendiendo la clase Thread
- crear una nueva clase que herede (extends) de la clase Thread;
- redefinir en la nueva clase el método run con el código asociado al hilo.
- crear un objeto de la nueva clase Thread.
- Invocar al método start del objeto Thread (el hilo que hemos creado).
4.2.- Creación de hilos mediante la interfaz Runnable
Para definir y crear hilos implementando la interfaz Runnable:
- declarar una nueva clase que implementa Runnable;
- redefinir en esa nueva clase el método run
- crear un objeto de la nueva clase;
- crear un objeto de tipo Thread pasando como argumento al constructor,
Para ponerlo en marcha:
- invocar al método start del objeto Thread
5.- Estados de un hilo
El ciclo de vida de un hilo:
- nuevo (NEW): se ha creado un nuevo hilo, pero aún no está disponible para su ejecución;
- ejecutable (RUNNABLE): el hilo está preparado para ejecutarse.
- no ejecutable o detenido (NO_RUNNABLE): el hilo podría estar ejecutándose, pero hay alguna actividad interna al propio hilo que se lo impide.
- muerto o finalizado (TERMINATED): el hilo ha finalizado.
El método getState de la clase Thread, permite obtener en cualquier momento el estado del hilo.
5.2.- Detener temporalmente un hilo
- el hilo se ha dormido. Se ha invocado al método sleep de la clase Thread, indicando el tiempo que el hilo permanecerá deteniendo
- el hilo está esperando. El hilo ha detenido su ejecución mediante la llamada al método wait, y no se reanudará, pasará a «ejecutable» (en concreto «preparado») hasta que se produzca una llamada al método notify o notifyAll por otro hilo del mismo proceso.
- el hilo se ha bloqueado. El hilo está pendiente de que finalice una operación de E/S en algún dispositivo.
5.3.- Finalizar un hilo
- crear un nuevo hilo con new;
- iniciar el hilo con start.
método isAlive de la clase Thread para comprobar si un hilo está vivo o no, devuelve verdadero (true) o falso (false).
5.4.- Dormir un hilo con sleep
- Pasarle como argumento como parámetro: sleep (long milisegundos)
- Indicar el tiempo extra que se sumará al segundo: sleep (long milisegundos, int nanosegundos)
6.- Gestión y planificación de hilos
- paralelismo. En un sistema con múltiples CPU o núcleos,
- pseudoparalelismo. Si no es posible el paralelismo, una CPU es responsable de ejecutar múltiples hilos.
6.1.- Prioridad de hilos
- MAX_PRIORITY (= 10)
- MIN_PRIORITY (=1)
- NORM_PRIORITY (= 5). (la que tiene el hilo donde corre el método main())
- getPriority. Obtiene la prioridad de un hilo.
- setPriority. Modifica la prioridad de un hilo.
Los hilos demonios son aquellos que se ejecutan en segundo plano.
7.- Sincronización y comunicación de hilos
- Dos o más hilos compiten por obtener un mismo recurso.
- Dos o más hilos colaboran para obtener un fin común
- La sincronización es la capacidad de informar de la situación de un hilo a otro.
- La comunicación es la capacidad de transmitir información desde un hilo a otro.
- monitores. Se crean al marcar bloques de código con la palabra synchronized;
- semáforos. indicador de condición que registra si un recurso está disponible o no.
- Notificaciones. Permiten comunicar hilos mediante los métodos wait, notify y notifyAll de la clase java.lang.Object.
7.1.- Información compartida entre hilos
- exclusión mutua. Asegurar que un hilo tiene acceso a la sección crítica de forma exclusiva y por un tiempo finito.
- por condición. Asegurar que un hilo no progrese hasta que se cumpla una determinada condición.
7.2.- Monitores. Métodos synchronized
- un método completo,
- cualquier segmento de código.
7.3.- Monitores. Segmentos de código synchronized
- Se le indica al método el objeto que se quiere sincronizar.
- Llamada al método que se quiere sincronizar.
- El hilo que entra en el segmento declarado synchronized se hará con el monitor del objeto, si está libre, o se bloqueará en espera de que quede libre.El monitor se libera al salir el hilo del segmento de código synchronized.
- Sólo un hilo puede ejecutar el segmento synchronized a la vez.
- La adquisición y liberación de monitores genera una sobrecarga.
- Siempre que sea posible, por legibilidad del código, es mejor sincronizar métodos completos.
- Al declarar bloques synchronized puede aparecer un nuevo problema, denominado interbloqueo (lo veremos más adelante).
7.4.- Comunicación entre hilos con métodos de java.lang.Object
Métodos para la comunicación entre hilos:
- wait(). Detiene el hilo (pasa a «no ejecutable»), el cual no se reanudará hasta que otro hilo notifique que ha ocurrido lo esperado.
- wait(long tiempo). Como el caso anterior pero pasando el tiempo por parámetro.
- notify(). Notifica a uno de los hilos puestos en espera para el mismo objeto, que ya puede continuar.
- notifyAll(). Notifica a todos los hilos
La llamada a estos métodos se realiza dentro de bloques synchronized.
7.5.- El problema del interbloqueo (deadlock)
- Porque cada hilo espera a que le llegue un aviso de otro hilo que nunca le llega.
- Porque todos los hilos, de forma circular, esperan para acceder a un recurso.
7.6.- La clase Semaphore
- Indicar al constructor Semaphore (int permisos) se le pasa el número de hilos que pueden acceder a la vez al recurso.
- Indicar al semáforo mediante el método acquire(), que queremos acceder al recurso, o bien mediante acquire(int permisosAdquirir) cuántos permisos se quieren consumir al mismo tiempo.
- Indicar al semáforo mediante el método release(), que libere el permiso, o bien mediante release(int permisosLiberar), cuantos permisos se quieren liberar al mismo tiempo.
- Hay otro constructor Semaphore (int permisos, boolean justo) que mediante el parámetro justo permite garantizar que el primer hilo en invocar adquire() será el primero en adquirir un permiso cuando sea liberado.
- Si se usa para proteger secciones críticas.
- Si se usa para comunicar hilos.
7.7.- La clase Exchanger
.
Existen dos métodos definidos en esta clase:
El funcionamiento:
7.8.- Las clase CountDownLatch.
Los aspectos más importantes al usar la clase CountDownLatch son los siguientes:
7.9.- La clase CyclicBarrier.
El funcionamiento:
Los aspectos más importantes:
8.- Aplicaciones multihilo.
La corrección de la aplicación se mide:
Tener en cuenta los siguientes aspectos:
Ventajas:
8.2.- La interfaz Executor y los pools de hilos.
Executors:
8.3.- Gestión de excepciones.
Para crear un manejador de excepciones:
8.4.- Depuración y documentación. Métodos de la clase thread: