Threads LSUB GSYC 30 de marzo de 2016 (cc) 2015 Laboratorio de Sistemas, Algunos derechos reservados. Este trabajo se entrega bajo la licencia Creative Commons Reconocimiento NoComercial - SinObraDerivada (by-nc-nd). Para obtener la licencia completa, véase http://creativecommons.org/licenses/. También puede solicitarse a Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. Las imágenes de terceros conservan su licencia original. Threads I ¿Concurrencia? I Un thread es un hilo de ejecución. I Una aplicación tiene al menos un thread. I Los threads de Java son expulsivos. La correspondencia entre threads y procesos del OS depende de la implementación. I Antes de usar algo desde distintos threads debemos asegurarnos de que es thread-safe. Thread I La clase Thread proporciona la abstracción. I Hay dos formas de crear una instancia de Thread: I I I Crear una clase que derive de la clase Thread. Crear una clase que implemente la interfaz Runnable. Se recomienda usar esta aproximación: separar el trabajo (tu clase Runnable) del trabajador (la clase Thread). Además, puedes ser Runnable y extender otra clase distinta a Thread. El thread arranca cuando se invoca su método start(). Clase con interfaz Runnable p u b l i c c l a s s Tweeter implements Runnable { @Override p u b l i c void run ( ) { f o r ( i n t i = 0 ; i < 1 0 ; i ++) System . o u t . p r i n t l n ( ” Thread s a y s t w e e e e e t ! ! ! ” ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( i n t i = 0 ; i < 1 0 ; i ++) ( new Thread ( new T w e e t e r ( ) ) ) . s t a r t ( ) ; } } Clase Thread anónima public class Servidor { public void atender () { f i n a l Socket sk ; // . . . a c e p t a r l l a m a d a en s k new Thread ( ) { p u b l i c void run ( ) { s e r v i r C l i e n t e ( sk ) ; }; }. start ( ) ; ... protected void s e r v i r C l i e n t e ( Socket sk ) { // . . . } } Thread Thread proporciona varios métodos de clase interesantes: I currentThread(): retorna la referencia al thread que está ejecutando. I activeCount(): retorna el número actual de threads activos. I dumpStack(): vuelca la pila del thread actual en la salida de errores. I sleep(int ms): causa que el thread actual se bloquee durante un tiempo. I yield(): causa que el thread actual ceda su cuanto a otro thread. System . o u t . p r i n t l n ( ” Thread ” + Thread . c u r r e n t T h r e a d ( ) . g e t I d ( ) + ” State ” + Thread . c u r r e n t T h r e a d ( ) . g e t S t a t e ( ) ) ; Interrupciones I Una interrupción es una indicación para que el thread deje de hacer lo que está haciendo para hacer otra cosa. I ¡No es aconsejable usar interrupciones! I Normalmente los threads reaccionan acabando su ejecución, retornando del método run(). Interrupciones I Algunos métodos elevan una InterruptedException si se interrumpe el thread mientras que ejecutan (p. ej. sleep()). I Otros métodos bloqueantes no levantan la excepción. I Se puede comprobar si el thread ha sido interrumpido invocando Thread.interrupted() si no se invocan métodos que eleven una InterruptedException. I ¡Ojo! Thread.interrupted() comprueba si ha sido interrumpido pero limpia el estado de interrupción si devuelve true. Código p u b l i c void run (){ ... try { ... Thread . s l e e p ( 1 0 0 0 ) ; ... } catch ( I n t e r r u p t e d E x c e p t i o n e ) { // E l t h r e a d ha s i d o i n t e r r u m p i d o m i e n t r a s d o r m i a // y no ha d o r m i d o e l t i e m p o d e s e a d o . R e to r na m os // de r u n ( ) System . e r r . p r i n t l n ( ” Thread ” + Thread . c u r r e n t T h r e a d ( ) . g e t I d ( ) +” i n t e r r u p t e d , e x i t i n g . ” ) ; return ; } ... } Código try { ... /∗ ∗ S i e l t h r e a d ha s i d o i n t e r r u m p i d o p e r o no ha s a l t a d o l a ∗ e x c e p c i o n , d e j a m o s que l o ma neje e l c a t c h l e v a n t a n d o ∗ n o s o t r o s mismos l a e x c e p c i o n ! interrupted () limpia ∗ e l e s t a d o , p e r o e s t e t h r o w l o p o n d r a de n ue vo . ∗/ i f ( Thread . i n t e r r u p t e d ( ) ) throw new I n t e r r u p t e d E x c e p t i o n ( ) ; ... } catch ( I n t e r r u p t e d E x c e p t i o n e ) { ... } Join I Se puede esperar a que un thread muera con join(). I Se le puede pasar un tiempo de espera. Thread t = new Thread ( new Worker ( ) ) ; t . start (); ... t . join (); Sincronización I Si dos threads acceden al mismo recurso compartido sin estar sincronizados, tendremos una condición de carrera. I Java proporciona dos mecanismos básicos de sincronización: I I Métodos synchronized. Sentencias synchronized. Métodos synchronized I Es un monitor. I No es posible que dos invocaciones a métodos synchronized del mismo objeto sean concurrentes. I Si un thread está ejecutando un método synchronized, cualquier otro thread que intente invocar un método synchronized de ese objeto se queda bloqueado hasta que pueda proceder. I Los constructores no pueden ser synchronized (no tiene sentido). Métodos synchronized I Internamente está implementado mediante un lock (intrinsic lock o monitor lock). I Toda instancia tiene asociada un monitor lock. I Las clases también tienen su propio lock para sincronizar el acceso a los miembros de clase (métodos estáticos synchronized, etc.). I Un thread que necesite acceso exclusivo a la instancia necesita adquirir el lock y después de acceder necesita soltar el lock. Al invocar un método synchronized se adquiere el lock automáticamente y se suelta al finalizar la invocación (por un return o por una excepción). I Es un lock re-entrante: desde un método synchronized se puede invocar a otro método synchronized. Código p u b l i c c l a s s Counter{ private int c ; public synchronized int i nc r () { r e t u r n ++c ; } } p u b l i c c l a s s Tweeter implements Runnable { p r i v a t e Counter counter ; p u b l i c T w e e t e r ( C o u n t e r c ){ counter = c ; } @Override p u b l i c void run ( ) { f o r ( i n t i = 0 ; i < 1 0 ; i ++){ System . o u t . p r i n t l n ( ” Thread ” + Thread . c u r r e n t T h r e a d ( ) . g e t I d ( ) + ” s a y s t w e e e e e t #” + counter . incr () + ” ! ! ! ” ) ; } } } Sentencias synchronized I Se debe especificar el objeto del que se quiere adquirir el lock. I Un método synchronized es lo mismo que poner synchronized(this) alrededor de todo el método. p u b l i c v o i d i n c r C o u n t e r ( S t r i n g name ) { synchronized ( this ) { c o u n t ++; } } wait() I wait() bloquea el thread, saliendo del monitor. I ¡Siempre se debe comprobar la condición antes de continuar! // d e n t r o de un metodo s y n c h r o n i z e d de l a c l a s e S e r v : // un t h r e a d e s p e r a a que l l e g u e un c l i e n t e w h i l e ( n c l i e n t s == 0 ) { try { wait ( ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { ... } } notify() I notify() despierta a un thread (cualquiera) de los que están bloqueados en un wait, que competirá por coger el lock del monitor. I notifyAll() despierta a todos. I Sólo debe invocarse desde dentro del monitor. // d e n t r o de o t r o metodo s y n c h r o n i z e d de l a c l a s e S e r v : // l l e g a un c l i e n t e y s e d e s p i e r t a a un t h r e a d // p a r a que l o a t i e n d a addClient (c ); n c l i e n t s ++; notifyAll (); Volatile I Las variables volatile se leen/escriben de forma atómica. I La escritura de una variable volatile fuerza una relación pasa-antes-que: si un thread ve la variable modificada, ve el estado generado por cualquier acción previa del thread que la modificó. I Ojo: esto no nos protege de la mayorı́a de las condiciones de carrera. p u b l i c c l a s s Counter { p r i v a t e v o l a t i l e long count = 0; public void increment () { c o u n t ++; // CONDICION DE CARRERA ! ! ! } } Executor El paquete java.util.concurrent ofrece la interfaz Executor: I Es una interfaz para lanzar nuevas tareas y evitar el idiom para crear un thread. I Sólo tiene un método, execute(). Los detalles de la ejecución dependen de la implementación concreta de la interfaz (ejecución inmediata, esperar un worker, etc.). I No hay forma de obtener el resultado de la tarea. Estas dos sentencias pueden ser equivalentes: ( new Thread ( r ) ) . s t a r t ( ) ; e . execute ( r ) ; ExecutorService I Extiende la interfaz Executor con submit() que permite pasar objetos que implementen Runnable. Retorna un objeto Future que sirve para consultar y controlar el progreso de la tarea. I shutdown() impide ejecutar nuevas tareas, pero deja que las que están en marcha puedan acabar. I shutdownNow() acaba las tareas en marcha y retorna una lista de tareas que estaban esperando para ejecutar. No da garantı́as sobre lo que pasará (si terminarán, se pararán, etc.). I awaitTermination() se bloquea hasta que termine el proceso de shutdown o salte el timeout. I La clase Executors proporciona factory methods para los distintos ExecutorServices. ExecutorService E x e c u t o r S e r v i c e pool = Executors . newFixedThreadPool ( 1 0 ) ; ... p o o l . e x e c u t e ( new C l i e n t M g r ( ) ) ; ... p o o l . shutdown ( ) ; i f ( ! p o o l . a w a i t T e r m i n a t i o n ( 6 0 , TimeUnit . SECONDS ) ) { p o o l . shutdownNow ( ) ; i f ( ! p o o l . a w a i t T e r m i n a t i o n ( 6 0 , TimeUnit . SECONDS ) ) System . e r r . p r i n t l n ( ” P o o l d i d n o t t e r m i n a t e ” ) ; } Futures I El método submit() permite pasar al executor una clase que implemente Runnable. I Retorna un objeto Future que sirve para consultar y controlar el progreso de la tarea. I El método get() del Future se bloquea y retorna null cuando la tarea ha terminado. ExecutorService E x e c u t o r S e r v i c e pool = Executors . newFixedThreadPool ( 1 0 ) ; F u t u r e <?> f u t u r e = p o o l . s u b m i t ( new R u n n a b l e ( ) { p u b l i c void run (){ try { Thread . s l e e p ( 2 0 0 0 ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { e . printStackTrace ( ) ; } } }); i f ( f u t u r e . g e t ( ) == n u l l ) System . o u t . p r i n t l n ( ” Termino ok ” ) ; ExecutorService I El método submit() también permite pasar objetos que implementen Callable. I Un objeto Callable debe implementar un método call() que retorna un valor. I El valor retornado por call() se obtiene con el método get() del objeto Future. ExecutorService E x e c u t o r S e r v i c e pool = Executors . newFixedThreadPool ( 1 0 ) ; F u t u r e <S t r i n g > f u t u r e = p o o l . s u b m i t ( new C a l l a b l e <S t r i n g >(){ p u b l i c S t r i n g c a l l ( ) throws Exception { System . o u t . p r i n t l n ( ” H o l a ! ! ” ) ; r e t u r n ”He t e r m i n a d o b i e n ! ! ” ; } }); System . o u t . p r i n t l n ( ” Cadena de s a l i d a de l a t a r e a : ” + future . get ( ) ) ; Colecciones concurrentes El paquete java.util.concurrent proporciona clases con estas interfaces: I BlockingQueue: FIFOs bloqueantes, ya las conocemos. I ConcurrentMap: Diccionarios (nombre-valor) con operaciones atómicas. BlockingQueue I Es una cola para comunicar procesos. I Amplı́a la interfaz Queue con operaciones bloqueantes. BlockingQueue p u b l i c i n t e r f a c e B l o c k i n g Q u e u e <E> e x t e n d s Queue<E> { boolean put (E e ) ; // como add , p e r o b l o q u e a E take ( ) ; // como e l e m e n t ( ) , p e r o b l o q u e a } BlockingQueue I Bloqueantes sin timeout: I I I put(e): inserta un elemento. take(): elimina la cabeza y la devuelve. Bloqueantes con timeout: I I offer(e, time, unit): inserta un elemento. poll(time, unit): elimina la cabeza y la devuelve. Thread-safeness I Podemos crear una colección envuelta con decorators para hacerla thread-safe. I Para ello podemos usar los métodos de Collections synchronizedSet o synchronizedMap p u b l i c s t a t i c <T> Set<T> s y n c h r o n i z e d S e t ( Set<T> s ) ; I Por ejemplo: S e t s = C o l l e c t i o n s . s y n c h r o n i z e d S e t ( new H a s h S e t ( ) ) ;