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 Concurrencia? Un thread es un hilo de ejecución. Una aplicación tiene al menos un thread. Los threads de Java son expulsivos. La correspondencia entre threads y procesos del OS depende de la implementación. Antes de usar algo desde distintos threads debemos asegurarnos de que es thread-safe.
Thread La clase Thread proporciona la abstracción. Hay dos formas de crear una instancia de Thread: 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 { @ O v e r r i d e p u b l i c v o i d run ( ) { f o r ( i n t i = 0 ; i < 1 0 ; i ++) System. out. 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 Tweeter ( ) ) ). s t a r t ( ) ;
Clase Thread anónima p u b l i c c l a s s S e r v i d o r { p u b l i c v o i d a t e n d e r ( ) { f i n a l Socket sk ; //... a c e p t a r l l a m a d a en sk... new Thread ( ) { p u b l i c v o i d run ( ) { s e r v i r C l i e n t e ( sk ) ; ;. s t a r t ( ) ; p r o t e c t e d v o i d s e r v i r C l i e n t e ( Socket sk ) { //...
Thread Thread proporciona varios métodos de clase interesantes: currentthread(): retorna la referencia al thread que está ejecutando. activecount(): retorna el número actual de threads activos. dumpstack(): vuelca la pila del thread actual en la salida de errores. sleep(int ms): causa que el thread actual se bloquee durante un tiempo. yield(): causa que el thread actual ceda su cuanto a otro thread. System. out. 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 t a t e + Thread. c u r r e n t T h r e a d ( ). g e t S t a t e ( ) ) ;
Interrupciones Una interrupción es una indicación para que el thread deje de hacer lo que está haciendo para hacer otra cosa. No es aconsejable usar interrupciones! Normalmente los threads reaccionan acabando su ejecución, retornando del método run().
Interrupciones Algunos métodos elevan una InterruptedException si se interrumpe el thread mientras que ejecutan (p. ej. sleep()). Otros métodos bloqueantes no levantan la excepción. Se puede comprobar si el thread ha sido interrumpido invocando Thread.interrupted() si no se invocan métodos que eleven una InterruptedException. 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 v o i d run ( ) {... t r y {... 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 dormia // y no ha dormido e l tiempo deseado. Retornamos // de run ( ) 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. ) ; r e t u r n ;...
Código t r y {... / 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, dejamos que l o maneje 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! i n t e r r u p t e d ( ) l i m p i a e l estado, p e r o e s t e throw l o pondra de nuevo. / 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 Se puede esperar a que un thread muera con join(). Se le puede pasar un tiempo de espera. Thread t = new Thread ( new Worker ( ) ) ; t. s t a r t ( ) ;... t. j o i n ( ) ;
Sincronización Si dos threads acceden al mismo recurso compartido sin estar sincronizados, tendremos una condición de carrera. Java proporciona dos mecanismos básicos de sincronización: Métodos synchronized. Sentencias synchronized.
Métodos synchronized Es un monitor. No es posible que dos invocaciones a métodos synchronized del mismo objeto sean concurrentes. 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. Los constructores no pueden ser synchronized (no tiene sentido).
Métodos synchronized Internamente está implementado mediante un lock (intrinsic lock o monitor lock). Toda instancia tiene asociada un monitor lock. Las clases también tienen su propio lock para sincronizar el acceso a los miembros de clase (métodos estáticos synchronized, etc.). 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). 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{ p r i v a t e i n t c ; p u b l i c s y n c h r o n i z e d i n t i n c r ( ) { r e t u r n ++c ; public c l a s s Tweeter implements Runnable { p r i v a t e Counter counter ; p u b l i c Tweeter ( Counter c ){ c o u n t e r = c ; @ O v e r r i d e p u b l i c v o i d run ( ) { f o r ( i n t i = 0 ; i < 1 0 ; i ++){ System. out. 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 # + c o u n t e r. i n c r ( ) +!!! ) ;
Sentencias synchronized Se debe especificar el objeto del que se quiere adquirir el lock. 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 ) { s y n c h r o n i z e d ( t h i s ) { count++;
wait() wait() bloquea el thread, saliendo del monitor. 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) { t r y { w a i t ( ) ; catch ( I n t e r r u p t e d E x c e p t i o n e ) {...
notify() notify() despierta a un thread (cualquiera) de los que están bloqueados en un wait, que competirá por coger el lock del monitor. notifyall() despierta a todos. 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 // para que l o a t i e n d a a d d C l i e n t ( c ) ; n c l i e n t s ++; n o t i f y A l l ( ) ;
Volatile Las variables volatile se leen/escriben de forma atómica. 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ó. 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 l o n g count = 0 ; p u b l i c v o i d i n c r e m e n t ( ) { count++; // CONDICION DE CARRERA!!!
Executor El paquete java.util.concurrent ofrece la interfaz Executor: Es una interfaz para lanzar nuevas tareas y evitar el idiom para crear un thread. 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.). 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. e x e c u t e ( r ) ;
ExecutorService 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. shutdown() impide ejecutar nuevas tareas, pero deja que las que están en marcha puedan acabar. 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.). awaittermination() se bloquea hasta que termine el proceso de shutdown o salte el timeout. 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 p o o l = E x e c u t o r s. 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 ( Pool d i d not t e r m i n a t e ) ;
Futures El método submit() permite pasar al executor una clase que implemente Runnable. Retorna un objeto Future que sirve para consultar y controlar el progreso de la tarea. 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 p o o l = E x e c u t o r s. newfixedthreadpool ( 1 0 ) ; Future <?> f u t u r e = p o o l. submit ( new Runnable ( ) { p u b l i c v o i d run ( ) { t r y { 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. p r i n t S t a c k T r a c e ( ) ; ) ; i f ( f u t u r e. g e t ( ) == n u l l ) System. out. p r i n t l n ( Termino ok ) ;
ExecutorService El método submit() también permite pasar objetos que implementen Callable. Un objeto Callable debe implementar un método call() que retorna un valor. 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 p o o l = E x e c u t o r s. newfixedthreadpool ( 1 0 ) ; Future <S t r i n g > f u t u r e = p o o l. submit ( 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 E x c e p t i o n { System. out. p r i n t l n ( Hola!! ) ; r e t u r n He t e r m i n a d o b i e n!! ; ) ; System. out. p r i n t l n ( Cadena de s a l i d a de l a t a r e a : + f u t u r e. g e t ( ) ) ;
Colecciones concurrentes El paquete java.util.concurrent proporciona clases con estas interfaces: BlockingQueue: FIFOs bloqueantes, ya las conocemos. ConcurrentMap: Diccionarios (nombre-valor) con operaciones atómicas.
BlockingQueue Es una cola para comunicar procesos. Ampĺıa la interfaz Queue con operaciones bloqueantes.
BlockingQueue p u b l i c i n t e r f a c e BlockingQueue<E> extends Queue<E> { boolean put (E e ) ; // como add, p e r o b l o q u e a E t a k e ( ) ; // como e l e m e n t ( ), p e r o b l o q u e a
BlockingQueue Bloqueantes sin timeout: put(e): inserta un elemento. take(): elimina la cabeza y la devuelve. Bloqueantes con timeout: offer(e, time, unit): inserta un elemento. poll(time, unit): elimina la cabeza y la devuelve.
Thread-safeness Podemos crear una colección envuelta con decorators para hacerla thread-safe. 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 ) ; Por ejemplo: Set 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 HashSet ( ) ) ;