Documente Academic
Documente Profesional
Documente Cultură
Hilos
MSC Quintzin Reyes Gmez
Qu es un hilo?
Imagina un programa de Java que lee archivos grandes en Internet en distintos servidores. Algunos de ellos estarn sometidos a grandes cargas o tendrn conexiones lentas a Internet. Otros tal vez devuelvan datos con rapidez. La mayor parte del tiempo, nuestro programa estar esperando datos de la red. Un enfoque de programacin parece bastante claro: Leer archivo 1 del servidor A Leer archivo 2 del servidor B Leer archivo 3 del servidor C ....
2
Realizar estas lecturas de forma secuencial no resulta eficaz, ya que la carga del archivo 2 desde el servidor B no comenzar hasta que se haya cargado el archivo 1 por completo. Un enfoque mucho ms rpido sera leer desde cada archivo al mismo tiempo y gestionar los archivos parciales a medida que lleguen. Esto requiere la capacidad de tener varias tareas procesndose en paralelo (como si cada una estuviese asignada a un procesador independiente).
22/11/2011
La mayora de los ordenadores slo tienen un procesador, as que lo que realmente necesitamos es que el programa pueda conmutar a medida que lleguen los orgenes de datos. De forma ms general, deberemos poder escribir programas en los que el "flujo de control" se ramifique y donde estas ramificaciones se procesen en paralelo. El procesador puede conseguir esto conmutando entre las distintas ramas del programa en pequeos incrementos de tiempo. sta es la estrategia de los hilos.
Hilos y procesos
La mayora de sistemas operativos permiten la ejecucin de varios procesos en paralelo. Los procesos resultan costosos, pero son seguros. Estn tan bien aislados los unos de los otros que a veces resulta complicado y costoso incluso que se comuniquen entre s. Los hilos son ms asequibles, pero no estn bien aislados entre s cuando ejecutan el mismo proceso.
22/11/2011
Esto significa que Java es inherentemente un lenguaje multihilo. El entorno del tiempo de ejecucin de Java utiliza hilos simples incluso si el programa del usuario no lo hace. Pero los programadores tambin pueden utilizar hilos en su propio cdigo. Nuestra estrategia de descarga de varios archivos requiere el uso de hilos.
Simplifiquemos
La clase Thread proporciona la compatibilidad de Java para los hilos. Menos es siempre ms cuando hablamos de hilos. Siempre debe simplificar el uso de los hilos tanto como sea posible (simplificar, no hacerlo ms fcil).
22/11/2011
10
Puede escribir una clase por separado que implemente la interfaz Runnable, que contiene un mtodo nico:
public interface Runnable { public void run(); }
Se crea el hilo con Runnable como argumento del constructor. Una razn para utilizar este enfoque es que las clases de Java solamente pueden heredar de una clase nica. Si desea definir el mtodo run() de un hilo en una clase que ya herede de otra, no puede recurrir a la primera estrategia.
11
Si quisiramos que una instancia de la clase FrameInThread se ejecutase en su propio hilo, podramos utilizar la sentencia
Thread t = new Thread(new FrameInThread() );
12
22/11/2011
Cmo se detiene un hilo y se destruye?: Deja que termine el mtodo run() y que la referencia del hilo finalice o establezca la referencia en null. El recolector de basura reclamar el almacenamiento del hilo. t.stop() se despreciar.
13
O puedes esperarlo:
t.join(); // bloquea hasta que t finaliza
14
15
22/11/2011
URLCopyThreadMain
public class URLCopyThreadMain { public static void main(String argv[]) { String[][] fileList = { {"http://web.mit.edu/1.00/www/Lectures/Lecture28/ Lecture28.pdf", "Lecture28.pdf"}, {"http://microscopy.fsu.edu/micro/gallery/dinosau r/dino1.jpg" , "dino1.jpg"}, {"http://www.boston.com/","globe.html"}, {"http://java.sun.com/docs/books/tutorial/index.h tml", "tutorial.index.html"}, };
16
for (int i=0; i<fileList.length; i++) { Thread th; String threadName = new String( "T" + i ); th = new URLCopyThread( threadName, fileList[i][0], fileList[i][1] ); th.start(); System.out.println("Hilo " + th.getName() + " para copiar desde " + fileList[i][0] + " en " + fileList[i][1] + " iniciado" ); } } }
17
URLCopyThread
import java.io.*; import java.net.*; public class URLCopyThread extends Thread { private URL fromURL; private BufferedInputStream input; private BufferedOutputStream output; private String from, to;
18
22/11/2011
public URLCopyThread(String n, String f, String t) { super( n ); from = f; to = t; try { fromURL = new URL(from); input = new BufferedInputStream( fromURL.openStream()); output = new BufferedOutputStream( new FileOutputStream(to)); }
19
catch(MalformedURLException m) { System.err.println( "MalformedURLException creando URL + from); } catch(IOException io) { System.err.println("IOException " + io.toString() ); } }
20
public void run() { byte [] buf = new byte[ 512 ]; int nread; try { while((nread=input.read(buf,0,512)) > 0){ output.write(buf, 0, nread); System.out.println( getName() + ": " + nread + " bytes" ); }
21
22/11/2011
input.close(); output.close(); System.out.println("Hilo " + getName() + " copiando " + from + " en " + to + "finished"); } catch(IOException ioe) { System.out.println("IOException:" + ioe.toString()); } } // fin del mtodo run() } // fin de la clase URLCopyThread
22
Sincronizacin de hilos
Cuando los programas utilizan hilos, a menudo se deben solucionar los conflictos y las incoherencias que stos provocan. Los dos problemas ms significativos son la sincronizacin y el interbloqueo.
23
Sincronizacin: el problema
En muchos casos, un segmento de cdigo se puede ejecutar como "todo o nada" antes de poder ejecutar otro hilo. Por ejemplo, suponga que est insertando un nuevo objeto en un Vector y que el nuevo elemento supera la capacidad actual. El mtodo addElement() del vector deber copiar el contenido de Vector en una nueva ubicacin de la memoria con mayor capacidad y, entonces, agregar el nuevo elemento. Si esta operacin la ejecuta un hilo y ha finalizado parcialmente cuando otro hilo toma el control e intenta obtener un elemento del mismo Vector, aparece el problema: el primer hilo interrumpido deber abandonar el vector parcialmente copiado en un estado incoherente. 24
22/11/2011
Mtodos synchronized
Java permite declarar un mtodo como synchronized para evitar este tipo de problemas. Una definicin de mtodo como sta
public synchronized void foo() { // cuerpo del mtodo }
significa que foo() no puede ser interrumpido por otro mtodo synchronized que acte sobre el mismo objeto. Si otro hilo intentase ejecutar otro mtodo synchronized en el mismo objeto, este hilo debera esperar a que el primer mtodo synchronized finalizase.
25
26
Funcionamiento de la sincronizacin
Java implementa mtodos synchronized a travs de un bloque especial llamado monitor que forma parte de cada instancia de cada clase que hereda de Object. Cuando un hilo necesita entrar en un mtodo synchronized, intenta adquirir el bloqueo en el objeto actual. Si ningn otro mtodo synchronized llamado en este objeto se encuentra activo en el algn hilo, el bloqueo est libre y el hilo puede continuar. Pero si otro hilo est ejecutando un mtodo synchronized en el objeto, el bloque no estar libre y el primer mtodo deber esperar.
27
22/11/2011
Si un mtodo esttico est sincronizado, el bloque forma parte del objeto que representa la clase (una instancia de la clase Class).
28
Sincronizacin en el JDK
El truco reside en saber si un mtodo necesita ser sincronizado. Muchos mtodos de las clases predefinidas de Java ya estn sincronizados. Por ejemplo, la mayora de los mtodos de la clase Vector estn sincronizados por el motivo descrito anteriormente. Otro ejemplo: el mtodo de la clase Component de Java AWT que agrega un objeto MouseListener a un Component (para que MouseEvents se registren en el MouseListener) tambin est sincronizado.
29
Si comprueba el cdigo fuente de AWT y Swing, encontrar que la firma de este mtodo es
public synchronized void addMouseListener(MouseListener l)
30
10
22/11/2011
31
Interbloqueo
Cuando dos hilos distintos requieren acceso exclusivo a los mismos recursos, pueden darse situaciones en las que de ellos obtenga acceso al recurso que el otro hilo necesita. En ese caso ninguno de los dos podr continuar. Por ejemplo, suponga que cada hilo necesita privilegios exclusivos de escritura en dos archivos distintos. El hilo 1 podra abrir el archivo A de forma exclusiva y el hilo 2 hacer lo mismo con el archivo B. Ahora el hilo 1 necesita acceso exclusivo al archivo B y el hilo 2 necesita acceso exclusivo al archivo A. Ambos hilos se obstaculizan entre s. El origen ms comn de este problema tiene lugar cuando dos hilos intentan ejecutar mtodos synchronized en el mismo conjunto de objetos.
32
Ejemplo de interbloqueo
public class Value { private long value; public Value( long v ) { value=v; } synchronized long getValue() { return value; } synchronized void setValue( long v) { value=v; } synchronized void swapValue( Value other ) { long t = getValue(); long v = other.getValue(); setValue( v ); other.setValue(t); } }
33
11
22/11/2011
Diagrama de interbloqueo
Extrado de Doug Lea, Concurrent Programming in Java (2000). Excelente referencia para usuarios avanzados
34
35
Los programas deben funcionar correctamente sin tener en cuenta el orden o la secuencia en que se ejecutan los distintos hilos. En el momento en que se sincroniza para prevenir posibles interferencias perjudiciales entre hilos, el riesgo del interbloqueo aparece.
36
12
22/11/2011
Ejemplo de cronmetro
Como ejemplo ms complicado para ilustrar una interaccin elegante con Swing consideraremos la clase Clock que implementa un cronmetro. Clock implementa Runnable y, por tanto, puede utilizarse para crear su propio hilo. Time es una clase interna que proporciona la pantalla del tiempo del cronmetro.
38
Clock, main()
public class Clock extends JFrame implements Runnable { private Thread clockThread = null; private Time time; private long accumTime = 0L; private long startTime = -1L; public static void main( String[] args ) { Clock clock = new Clock(); clock.show(); }
39
13
22/11/2011
Clock, constructor
public Clock() { super( "Reloj" ); setDefaultCloseOperation( EXIT_ON_CLOSE ); JPanel buttons = new JPanel(); JButton bStart = new JButton( "iniciar" ); bStart.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { start(); } } ); // crea los botones bStop y bReset de la misma forma
40
Clock, constructor, 2
buttons.add( bStart ); buttons.add( bStop ); buttons.add( bReset ); Container content = getContentPane(); content.add( buttons, BorderLayout.NORTH ); time = new Time(); content.add( time, BorderLayout.CENTER ); setSize( 240, 120 ); startTime = System.currentTimeMillis(); }
41
Clock, start()
private void start() { if (clockThread == null) { clockThread = new Thread(this, "Reloj"); startTime = System.currentTimeMillis(); clockThread.start(); } }
42
14
22/11/2011
43
Clock, run()
public void run() { Thread myThread = Thread.currentThread(); while (clockThread == myThread) { time.repaint(); try { Thread.sleep(100); } catch (InterruptedException e){} } }
44
15
22/11/2011
46
Hilos y Swing
Todos los programas de Java ejecutan, al menos, tres hilos:
El hilo main(); es decir, el hilo que comienza con el mtodo main; El hilo de eventos, con el que el sistema de ventanas notifica sobre los eventos en los que se ha registrado y El hilo del recolector de basura.
El hilo del recolector de basura se ejecuta en segundo plano (con prioridad menor) y uno puede olvidarse incluso de que est ah. Pero, tan pronto como coloquemos una interfaz grfica de usuario, deberemos tener en cuenta el hilo de eventos.
47
Hilos y el AWT
El paquete de GUI inicial de Java, el AWT, sincronizaba varios mtodos en las clases de programacin. Pero haca que las clases del AWT estuviesen expuestas a interbloqueos. Cuando los programadores de Java se plantearon implementar capacidades mucho ms complejas de Swing, de hecho, abandonaron. El AWT intenta ser multihilo, esto es, permitir la llamada a clases desde varios hilos.
48
16
22/11/2011
Hilos y Swing
Dejando a un lado poqusimas excepciones, las clases de Swing esperan que sus mtodos se llamen nicamente desde el hilo de eventos. Tal como describen los desarrolladores de Java: "Una vez que un componente de Swing se detecta, todo el cdigo que afecte al estado de dicho componente o dependa de l debe ejecutarse en el hilo de entrega de eventos."
49
Un componente se detecta cuando el sistema de ventanas lo asocia a una ventana que realmente lo muestra en pantalla. Esto suele ocurrir cuando el componente se hace visible por primera vez o cuando se le otorga un tamao preciso por primera vez (mediante la llamada a pack(), por ejemplo). Hasta ese momento, se puede modificar desde cualquier otro hilo como el hilo principal, ya que no hay posibilidad de que se pueda acceder a l desde el hilo de eventos hasta que el sistema de ventanas no lo detecte.
50
As, puede agregar componentes (add()) al contenedor desde el hilo principal o agregar texto a un JTextArea, siempre y cuando no sea detectado. Ahora bien, una vez que se hace visible, puede aceptar clics del ratn o pulsaciones de teclas, o cualquier otro tipo de evento y pueden utilizarse los mtodos de llamada correspondientes. Swing NO sincroniza estos mtodos ni los mtodos a los que pueden llamar, como setText() o add(). Si quiere llamar a setText() o a mtodos similares desde cualquier otro hilo que no sea el de eventos, deber utilizar una tcnica especial.
51
17
22/11/2011
52
Uso de invokeLater()
Cmo creamos esta tarea?
Runnable update = new Runnable() { public void run() { component.doSomething(); }; SwingUtilities.invokeLater( update );
la
53
54
18