Threads

Anuncio
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 ( ) ) ;
Descargar