Programación de fíos con Java
Sumario
Introducción
Comezaremos aprendendo a crear e lanzar novos fios (threads) dun proceso con Java, e facer que diferentes fíos dun mesmo proceso colaboren e se coordinen entre sí para facer unha tarefa. No resto deste capítulo veranse os mecanismos e as clases que proporciona a linguaxe Java para a programación de fíos e a súa sincronización.
Utilidades de concurrencia
Java da soporte ó concepto de fío desde a propia linguaxe:
- con algunhas clases e interfaces definidas no paquete java.lang
- con novas utilidades para desenvolver aplicacións multihilo e cun alto nivel de concurrencia do paquete java.util.concurrent
- con métodos específicos para a manipulación de fíos na clase Object
Paquete java.lang
Disponemos dunha interfaz e as siguientes clases para traballar con fíos:
- Clase thread
- Clase responsable de producir fíos funcionais para outras clases e que proporciona gran parte dos métodos utilizados para a súa xestión
- Interfaz Runnable
- Proporciona a capacidade de engadir a funcionalidade de fío a unha clase simplemente implementando a interfaz en lugar de derivándoa da clase thread.
- Clase ThreadDeath
- Clase de error que deriva da clase Error e que proporciona medios para manexar e notificar erros
- Clase ThreadGroup
- Utilízase para manexar un grupo de fíos de xeito conxunto de modo que se poida controlar a súa execución de forma eficiente
- Clase Object
- Non é estrictamente de apoio ós fíos pero proporciona métodos imprescindibles dentro da arquitectura multifío de Java:
- wait()
- notify()
- notifyAll()
Podes consultar nestes enlaces ([1],[2]) un resumo da clase thread cos métodos máis comúns para xestionar e controlar fíos.
Paquete java.util.concurrent
Inclúe unha serie de clases que facilitan enormemente o desenvolvemento de aplicacións multifío complexas, xa que están concebidas para utilizarse como bloques de deseño.
Estas utilidades están dentro dos seguintes paquetes:
- java.util.concurrent.
- Neste paquete están definidos os seguientes elementos:
- Clases de sincronización:
- Semaphore
- CountDownLatch
- CyclicBarrier
- Exchanger
- Interfaces para separar a lóxica da execución, coma por exemplo:
- Executor
- ExecutorService
- Callable
- Future
- Interfaces para xestionar colas de fíos:
- BlockingQueque
- LinkedBlokingQueque
- ArrayBlockingQueque
- SynchronousQueque
- PriorityBlockingQueque
- DelayQueque
- Clases de sincronización:
- java.util.concurrent.atomic
- Inclúe un conxunto de clases para ser usadas coma variables atómicas en aplicaciones multifío e con diferentes tipos de dato. Por exemplo:
- AtomicInteger
- AtomicLong.
- java.util.concurrent.locks
- Define unha serie de clases coma uso alternativo á cláusula sinchronized. Neste paquete atopanse algunhas interfaces coma:
- Lock
- ReadWriteLock.
Creación de fíos en Java
Para executar un programa de Java, iníciase a máquina virtual de Java (JVM) cun único fío que executa o método main() dunha clase. Pero desde este fío podense crear máis fíos, a partir de calquera clase que implemente a interfaz Runnable. Nesta clase hai que programar o método run(), que se executa cando se lanza o fío.
Un fío xestionase con un obxecto da clase Thread. Despois de crealo podese lanzar co seu método start() . Con esto crease o fío e se executa o método start() .
O seguiente programa de exemplo lanza dous fios dun proceso. A funcionalidad dos fíos implementase nunha clase Fío, que implementa a interfaz Runnable. O lanzar o fío execútase o método run() , que escribe un identificador que se pasa no constructor.
1 package lanzahilos; 2 class Fio implements Runnable { 3 private final String nome; 4 Fio(String nome) { 5 this.nome = nome; 6 } 7 @Override 8 public void run() { 9 System.out.printf(“Ola, son o fío: %s.\n”, this.nome); 10 System.out.printf(“Fío %s terminado.\n”, this.nome); 11 } 12 } 13 public class LanzaFios { 14 public static void main(String[] args) { 15 Thread h1 = new Thread(new Fio(“H1”)); 16 Thread h2 = new Thread(new Fio(“H2”)); 17 h1.start(); 18 h2.start(); 19 System.out.println(“Fio principal terminado.”); 20 } 21 }
Se se executa o programa varias veces, pódese ver que en cada execución as instruccións dos tres fíos (o principal e os dous creados por él) intercalan a súa execución de diversas maneiras, e empiezan e rematan a súa execución en distintos órdenes.
A clase Thread
A clase Thread proporciona a funcionalidad esencial para a creación, execución e xestión de fíos.
O crado resume os principales métodos desta clase.
| Método | Funcionalidade |
|---|---|
| void run() | Execútase cando se lanza o fío. É o punto de entrada do fío, coma o método main() é o punto de entrada do proceso. |
| void start() | Lanza o fío. A JVM crea o fío e executa o seu método run() |
| static void sleep(long ms) static void sleep(long ms, long ns) |
Detén a execución do fío actualmente en execución durante un tempo, que se pode indicar en microsegundos ou nunha combinación de microsegundos e nanosegundos. |
| void join() void join(long ms) void join(long ms, long ns) |
Agarda a que termine o fío. Pódese indicar un tempo máximo de espera, ben en milisegundos, bien nunha combinación de milisegundos e nanosegundos. |
| public void interrupt() public boolean isInterrupted() public static boolean interrupted() |
O primero interrumpe a execución dun fío; o segundo verifica se foi interrumpido un fío; o terceiro (estático) verifica se foi interrumpida a execución do fío actual, e borra o estado de interrupción, de xeito que unha chamada posterior devolvería false , a menos que se volva a interrumpir. |
| boolean isAlive() | Comproba se o fío está vivo. Un fío está vivo cando foi iniciado e non rematou a súa execución. |
| int getPriority() void setPriority(int nuevaPrior) |
Podese asignar unha prioridade a un fío, e se pode obtener a prioridade dun fío. |
| static Thread currentThread() | Devolve un obxecto de clase Thread correspondente ó fío en execución actualmente. |
| long getId() | Devolve o identificador do fío |
| String getName() void setName(String nombre) |
Pódese asignar un nome a un fillo, e se pode recuperar o nome do fío. |
| Thread.State getState() | Devolve o estado do fío. |
| boolean isDaemon() void setDaemon(boolean on) |
Un fío pode ser de tipo daemon. A distinción é importante porque a JVM termina a súa ejecución cando non queda ningún fío activo ou cando só quedan fíos de tipo daemon. |
O seguinte programa lanza dous fíos. Cada un deles fai pausas de duración aleatoria de ente 10 e 500 ms, utilizando o método sleep da clase Thread.
O fio principal utiliza o método join para esperar a que terminen os dous fillos lanzados, polo que seepre terminará o último. Os dous métodos anteriores pausan a execución do fío, e durante ese periodo de tempo se podería interrumpir. Se esto sucede, lanzaríase unha InterruptedException, que se captura para amosar unha mensaxe.
1 package lanzahilosyesperaqueterminen; 2 import java.util.Random; 3 class Hilo implements Runnable { 4 private final String nombre; 5 Hilo(String nombre) { 6 this.nombre = nombre; 7 } 8 @Override 9 public void run() { 10 System.out.printf(“Hola, soy el hilo: %s.\n”, this.nombre); 11 Random r = new Random(); 12 for (int i = 0; i < 5; i++) { 13 int pausa = 10 + r.nextInt(500 - 10); 14 System.out.printf(“Hilo: %s hace pausa de %d ms.\n”,this.nombre, pausa); 15 try { 16 Thread.sleep(pausa); 17 } catch (InterruptedException e) { 18 System.out.printf(“Hilo %s interrumpido.\n”, this.nombre); 19 } 20 } 21 System.out.printf(“Hilo %s terminado.\n”, this.nombre); 22 } 23 }
1 public class LanzaHilosYEsperaQueTerminen { 2 public static void main(String[] args) { 3 Thread h1 = new Thread(new Hilo(“H1”)); 4 Thread h2 = new Thread(new Hilo(“H2”)); 5 h1.start(); 6 h2.start(); 7 try { 8 h1.join(); 9 h2.join(); 10 } catch (InterruptedException e) { 11 System.out.println(“Hilo principal interrumpido.”); 12 } 13 System.out.println(“Hilo principal terminado.”); 14 } 15 }
Sincronización de fíos
Un programa pode lanzar múltiples fíos que colaboren entre sí para a realización dunha tarefa. Deben utilizarse mecanismos de sincronización para evitar problemas que se podan dar...
Interrupción de fios
Prioridades
Depuración (debugging) e documentación de aplicacións multifío
Podemos realizar seguementos da pila de Java tanto estáticos como dinámicos utilizando os seguintes métodos da clase thread:
- dumpStack(). Amosa unha traza da pila do fío (thread) en curso
- getAllStackTraces(). Amosa un Map de todos os fíos vivos na aplicación(Map é a interfaz hacia obxectos StackTraceElement que contén o nome do ficheiro, o número de liña e o nome da clase e o método da liña de código que se está executando)
- getStackTrace(). Devuelve o seguemento da pila dun fío nunha aplicación
Tanto getAllStackTraces() coma getStackTrace() permiten grabar os datos do seguemento de pila nun log.
Podes atopar un exemplo de cómo depurar aplicacións multifío desde o IDE de NetBeans e descargar ademáis as aplicacións de exemplo para practicar.
Para documentar as nosas aplicación de Java utilizaremos o xenerador JavaDoc, que de forma automática xenera a documentación da aplicación a partir do código fuente. Para isto este sistema consiste en incluir comentarios no código utilizando as etiquetas /** y */, que despois se procesarán para xenerar un conxunto de páxinas navegables HTML.
POdes atopar información máis detallada do uso de Javadoc nos recursos técnicos da web oficial de Oracle ou en moitos titoriales na web.
Mecanismos de alto nivel para concurrencia
Resumo
- Os fios en Java represéntanse mediante obxectos da clase Thread.
- Podense crear fíos a partir de obxectos de calquera clase que implemente a interfaz Runnable. A propia clase Thread implementa esta interfaz.
- O proceso que realiza un fío impleméntase no método run . Iníciase a execución dun fío co método start, que a súa vez executa o método run.
- O método join suspende a execución do fío actual á espera de que termine a execución do fío para o que se invoca.
- A palabra clave synchronized fai que un fío so poida executar un bloque de código tras obter o bloqueo dun obxecto de bloqueo, que se libera unha vez executado o bloque.
- Se o obxecto de bloqueo está bloqueado, suspéndese a execución do fío á espera de conseguir o bloqueo do obxecto de bloqueo. Deste xeito conseguese que o bloque de código se execute en exclusión mutua con outros fíos.
- Pódese aplicar:
- para métodos non estáticos (nese caso, o obxecto de bloqueo é o obxecto para o que se executa o método, this )
- para métodos estáticos (nese caso, o obxecto de bloqueo é a clase a que pertence ó método)
- para bloques de código cualesquiera (nese caso, especifícase explícitamente o obxecto de bloqueo).
- O interbloqueo ou deadlock prodúce cuando dous ou máis fíos manteñen bloqueados un conxunto de obxectos de bloqueo, e a súa execución está suspendida á espera de conseguir bloquear cada un un novo obxecto de bloqueo, que non poden bloquear porque xa están bloqueados por outros fíos.
- Podese evitar o interbloqueo establecendo unha orde para os obxectos de bloqueo, e todos os hilos se bloquean segundo este orde.
- Si, una vez conseguido el bloqueo sobre un objeto de bloqueo obj en un bloque de código synchronized(obj) , un hilo comprueba que no se dan las condiciones necesarias para continuar con su ejecución, puede ejecutar wait() sobre el objeto de bloqueo con obj.wait() . Con ello, su ejecución queda suspendida, y en espera no activa, mientras que otro objeto no ejecute obj.notifyAll() u obj.notify() . notifyAll() reanuda todos los procesos a la espera en el objeto de bloqueo, mientras que notify() reanuda solo uno de ellos.
- Podense incluir nunha clase os mecanismos de sincronización apropiados para permitir o uso dos seus obxectos por distintos fíos de xeito concurrente. Desa maneira, non é preciso implementar neses fíos ningún mecanismo de sincronización para controlar o acceso concurrente ós objetos da clase. Se dice entonces que la clase es thread-safe o segura para su uso concurrente por parte de varios hilos. O paquete java.util.concurrent inclúe una variedad de clases thread-safe.
- En xeral, deben programarse os fios de xeito que se permita a súa interrupción por parte doutros fíos. Para iso deben xestionar de maneira apropiada a excepción InterruptedException, comprobar cada certo tiempo se foron interrumpidos con isInterrupted() ou interrupted() , e realizar as accións oportunas no seu caso.
- Os entornos de desarrollo adoitan permitir a depuración de aplicacións multifío e proporcionar ferramentas para a detección de interbloqueos.
- o paquete java.util.concurrent proporciona mecanismos de alto nivel para concurrencia.
Créditos e referencias
- Programación de servicios y procesos. Carlos Alberto Cortijo Bon ISBN: 9788413571041
- Programación multifío
- Introducción a la Programación Multihilo
- Creación de Hilos y Multihilos con Ejemplos
- Multitaréa e Hilos, fácil y muchas ventajas
- Multitarea e Hilos en Java con ejemplos
- Multithreading con ejemplos