Está en la página 1de 17
a at 202_J2ME: MANUAL DE USUARIO Y TUTORIAL RAMA Displayable Command = wirjeracen oice Canvas Screen A, i ChoiceGroup| TextBox Spacer Alert Gauge ay DateField stem Ey ‘Customitem A | TextField Stringitem | [ Texttem | [ Imagettem Figura 11.1 EL PAQUETE LCDUI Todas las clases que intervienen en la creacién de interfaces de usuario proporcionadas por la implementacién de Sun Microsystems en el API de la especificacisn MIDP, se encuentran englobadas en el _paquete javax.microedition.Iedui. Las diferentes figuras de este capitulo muestran ejemplos de los elementos graficos generados mediante este paquete. El paquete contiene interfaces y clases que se veran en detalle en este capitulo, aunque se describen de forma sucinta a continuacién. Choice Interfaz que define un API para un componente que necesite implementar una seleccién entre un cierto numero de opciones. CommandListener Interfaz utilizada por las aplicaciones que necesitan recibir eventos de alto nivel desde la implementacién del sistema del dispositivo. ItemCommandListener Interfaz incorporada a la especificacién MIDP 2.0 para que las aplicaciones puedan recibir eventos de alto nivel desde componentes_interactivos incorporados en objetos de tipo Ferm. CAPITULO 11; TARAS Y MULTITAREA 215 Para controlar el estado de! appiet, se ha modificado el funcionamiento del objeto Listener que recibe los eventos del ratén, en donde se ha introducido la variable suspendido. Diferenciar los distintos estados de ejecucién del applet resulta importante porque algunos métodos pueden generar excepciones si son llamados desde un estado erréneo. Por ejemplo, si el applet ha sido arrancado y se detiene con stop(), y en ese momento se intenta ejecutar el método stari(), se generard una excepcidn MlegalThreadStateExeeption. ‘Aqui se puede poner de nuevo en cuarentena la idoneidad del uso de estos métodos para el control del estado de la tarea, tanto por lo comentado del posible bloqueo de recursos vitales del sistema, como porque se puede generar un punto muerto en el sistema si la tarea que va a intentar revivir la tarea suspendida necesita del recurso bloqueado. Por ello, es mas seguro el uso de una variable de control como suspendido, de tal forma que sca ella quien controle el estado de la tarea y utilizar el método notifv( para indicar cuando la tarea vuelve a la vida. ESTADOS DE UNA TAREA Durante cl ciclo de vida de una tarea, ésta se puede encontrar en diferentes _estados. La figura 11.2 muestra estos estados y los métodos que provocan el paso de un estado a otro. Este diagrama no es una maquina de estados finita, pero es lo que mas se aproxima al funcionamiento real de una tarea. new Teor yiaido start. Ejecutable Figura (1.2 Nueva tarea La siguiente sentencia crea una nueva tarea pero no la arranca, la deja cn cl estado de Nueva Tarea: Thread MiThread Thread MiThread new MiClaseThread() ; new Thread( new UnaClaseThread,"hiloA" ); Cuando una tarea esta en este estado, es simplemente un objeto Thread vacio. El sistema no ha destinado ningin recurso para él. Desde este estado solamente puede 216 JAVA 2:MANUAL DE USUARIO Y TUTORIAL RANA arrancarse Hamando al método start(), 0 detenerse definitivamente, llamando al método siop(); la Hamada a cualquier otro método carece de sentido y lo tinico que provocard serd la generacién de una excepcién de tipo MegalThreadStateException. Ejecutable Ahora observe el lector las dos lineas de cédigo que se presentan a continuacién: Thread MiThread = new MiClaseThreadQ ; MiThread. startQ; La llamada al método siart() crearé los recursos del sistema necesarios para que la tarea pueda ejecutarse, la incorpora a la lista de procesos disponibles para ejecucién del sistema y llama al método run() de la tarea. En este momento se encuentra en el estado Ejecutable del diagrama. Y este estado es Ejecutable y no En Ejecucién, porque cuando la tarea esta aqui no esta corriendo. Muchos ordenadores tienen solamente un procesador, lo que hace imposible que todas las tareas estén corriendo al mismo tiempo. Java implementa un tipo de scheduling o lista de procesos que permite — que el procesador sea compartido entre todos los procesos 0 tareas que se encuentran en la lista. Sin embargo, para el propdsito que aqui se persigue, y en la mayoria de los casos, se puede considerar que este estado es realmente un estado En Ejecucién, porque la impresién que produce ante el usuario es que todos los procesos se ejecutan al mismo tiempo. Cuando la tarea se encuentra en este estado, todas las instrucciones de cédigo que se encuentren dentro del bloque declarado para el método run(), se ejecutardn secuencialmente. Parada La tarea entra en estado Parada cuando alguien llama al método suspend(, cuanilo se Hama al método sleep(), cuando la tarea esté bloqueada en un proceso de entrada/salida 0 cuando la tarea utiliza su método waif() para esperar a que se cumpla una determinada condicion. Cuando ocurra cualquiera de las cuatro cosas anteriores, la tarea estard on estado Parada. Por ejemplo, en el trozo de cddigo siguiente ‘Thread Mi Thread MiThread. startQ; try f MiThread.sleep( 10000 ); } catch( Interruptedexception e ) { e.printStackTraceQ ; 2: new MiClaseThreadQ ; la llamada al método sleep() hace que la tarea se duerma durante 10 segundos. Durante ese tiempo, incluso aunque el procesador estuviese totalmente libre, SPAMA CAPITULO 11: TAREAS Y MULTITAREA 217 _ HiThread no correria. Después de esos 10 segundos. MiThread volveria a estar en estado Bjecutable y ahora si que el procesador podria hacerle caso cuando se encuentre disponible. Para cada uno de los cuatro modos de entrada en estado Parada, hay una forma especifica de volver a estado Ajecutable. Cada forma de recuperar ese estado es _ exolusiva; por ejemplo, si la tarea ha sido puesta a dormir, una vez. transcurridos los milisegundos que se especifiquen, ella sola se despierta y vuelve a estar en estado Becuiable. Lamar al método resume() mientras esté la tarea durmiendo no serviria para nada. Los métodos de recuperacién del estado Ejecutable, en funcién de la forma de llegar al estado Parada de la tarea, son los siguientes: * Si una tarea esta dormida, pasado el lapso de tiempo. » Si una tarea esta suspendida, después de una llamada a su método reswme(). © Si una tarea esté bloqueada en una entrada/salida, una vez que el comando de entrada/salida concluya su ejecucion. © Si una tarea esta esperando por una condicién, cada vez que la variable que controla esa condicion varie debe lamarse al metodo nozifi() 0 notifvAllO. Muerta Una tarea se puede morir de dos formas: por causas naturales o porque la maten. Una tarea muere normalmente cuando concluye de forma habitual su método run(). ‘or ejemplo, en el siguiente trozo de cédigo, el bucle while es un bucle finito, ya que iza la iteracién 20 veces y termina: Bilic void run© { “int i=0; /whileC i < 20) { aH; System.out.printIn( "i = “+i ); a} ae) Una tarea que contenga a este método run/(), morira naturalmente después de que < complete cl bucle y run() concluya. También se puede matar en cualquier momento una tarea, invocando a su método yp(). En el trozo de eddigo siguiente Thread MiThread Thread. start C new MiClaseThread() ; try £ “WiThread. sleep 10000 ); catch( InterruptedException e ) { “e.printStackTrace() ; 218_JAVA 2: MANUAL DE USUARIO Y TUTORIAL : RANA I MiThread. stop(); se orea y arranca la tarea MiThread, se duerme durante 10 segundos y en el momento de despertarse, la llamada a su método siop(), la mata. El método stop() envia un objeto ThreadDeath a la tarea que quiere detener. Asi, cuando una tarea es parada de este modo, muere asincronamente. La tarea morird en | mismo instante en que reciba ese objeto ThreadDeath. Los applets utilizaran el método sfop() para matar a todas sus tareas cuando el navegador con soporte Java en el que se estan ejecutando le indica al applet que se detengan, por ejemplo, cuando se minimiza la ventana del navegador o cuando se cambia de pagina. Hay que recordar que en la plataforma Java 2 el método s/op() de la clase Thread ya no estd implementado como tal, sino que debe ser implementado por el programador en cada una de las subclases que cree, preferiblemente para el control de una variable booleana que indique si la tarea esté corriendo o parada. SCHEDULING Java tiene un Scheduler, una lista de procesos, que monitoriza todas las tarees que se estan ejecutando en todos los programas y decide cudles deben ejecutarse y cuales deben encontrarse preparadas para su ejecucién. Hay dos caracteristicas de les tareas que el scheduler identifica en este proceso de decision. Una, la més importante, es la prioridad de la tarea; la otra, es el indicador de demonio. La regla basica del scheduler es que si solamente hay tareas demonio ejecutdndose, la Maquina Virtual Java (JVM) concluiré. Las nuevas tareas heredan la priotidad y el indicador do demonio de las tareas que los han creado. El scheduler determina qué tareas deberén ejecutarse comprobando 1a prioridad de todas ellas, aquéllas con prioridad més alta dispondran del procesador antes de las que tienen prioridad més baja, El scheduler puede seguir dos patrones, preemptive y no-preemptivo. 10s schedulers preemptivos proporcionan un segmento de tiempo a todas las tareas que estén corriendo en el sistema. El scheduler decide cual sera la siguiente tarea a gjecutarse y lama al método reswme() para darle vida durante un perfodo fijo de tiempo. Cuando la tarea ha estado en ejecucién ese perfodo de tiempo, se llama ¢ suspendd) y la siguiente tarea en la lista de procesos sera relanzada (con reswme()). Los schedulers no-preemptivos deciden la tarea que debe correr y la ejecutan hasta que concluye. La tarea tiene control total sobre el sistema mientras esté en ejecucién. Bl método yield”) es la forma en que una tarea fuerza al scheduler a comenzar la gjecucion de otra tarea que esté esperando. Dependiendo del sistema en que esté corriendo Java, el scheduler sera de un tipo u otro, preemptivo o no-preemptivo, ORAMA CAPITULO LI: TAREAS Y MULTITAREA 219 Prioridades El scheduler determina la tarea que debe ejecutarse en funcién de la prioridad asignada a cada una de ellas. El rango de prioridades oscila entre 1 y 10. La prioridad por defecto de una tarea es NORM_PRIORITY, que tiene asignado un valor de 5. Hay ottas dos variables estaticas disponibles, que son MIN_PRIORITY, fijada a 1, y MAX_PRIORTTY, que tiene un valor de 10. El método geéPrioriiv( puede utilizarse para conocer el valor actual de la prioridad de una tarea. Tareas demonio Las tareas demonio también se Tlaman servicios, porque se ejecutan, normalmente, con prioridad baja y proporcionan un servicio basico a un programa 0 programas cuando la actividad de la maquina es reducida, Son titiles cuando una tarea debe ejecutarse en segundo plano durante largos periodos de tiempo. Un ejemplo de farea demonio que esta en continua ejecucién es el recolector de basura (garbage coilector). Esta tarea, proporcionada por la Maquina Virtual Java, comprueba las variables de los programas a las que no se accede nunca y libera estos recursos, devolviéndolos al sistema. Una tarea puede fijar su indicador de demonio pasando un valor true al método ‘setDaemon(). Si se pasa false a este método, la tarea sera devuelta por el sistema como una tarea de usuario. No obstante, esto ultimo debe realizarse antes de que se atranque la tarea (mediante la Hamada al método sfart()). Si se quiere saber si una tarea es una tarea demonio, se utilizara el método isDaemon(). COMUNICACIONES ENTRE TAREAS Otra clave del éxito y la ventaja de la utilizacién de miltiples tareas en una ‘aplicacién, 0 aplicacién multithreaded, es que pueden comunicarse entre si. Se pueden diseiar tareas para utilizar objetos comunes, que cada tarea puede manipular independientemente de las otras tareas, Como se ha indicado en parrafos anteriores, una de las formas de controlar el “estado de una tarea es mediante el uso de condiciones de estado, invocando al método “Waitf) para que la tarea se inactive hasta que la condicién establecida varie y se inyoque al método notifv() para reanudar la ejecucién de la tarea. La utilizacién de “elas condiciones de control resulta util en muchas circunstancias, por ejemplo, quando una tarea no quiere concluir, sino solamente esperar hasta que un objeto “determinado se desbloquee. Y, concretamente, los mensajes, eventos, pulsaciones de teclado, peticiones web, ete., son ejemplos en los que las condiciones de control constituyen la base de su funcionamiento. 220 JAVA 2: MANUAL DE USUARIO Y TUTORIAL, | El ejemplo cldsico de comunicacién entre tareas es un modelo de tipo productor/consumidor, como el ya implementado en capitulos anteriores en base a — Colas. En este caso se implementa mediante un buffer y un monitor que actuard de controlador de las peticiones que se realicen entre productor y consumidor. Una tarea { produce una salida, que otra tarea usa (consume), sea lo que sea esa salida, Por ejemplo, se crea un producror, que sera una tarea que ira sacando caracteres por su salida; y se crea también un consumidor que iré recogiendo los caracteres que vaya sacando el productor y un monitor que controlaré el proceso de sincronizacién ente las tareas. Funcionaré como una tuberia, insertando el productor caracteres en un exiremo y leyéndolos el consumidor en el otro, con el monitor siendo la propia tuberia. Monitor lanzarCchar c) | recoser() ADE HCE wou Figura 11.3 Productor | El productor extenderd la clase Thread, y su cddigo es el siguiente: | class Productor extends Thread { private Tuberia tuberia; i private String alfabeto = "ABCDEFGHIJKLMNOPQRSTUVWXYZ public Productor( Tuberia t ) { // Mantiene una copia propia del objeto compartido tuberia = t; + public void rund) { char c3 // Mete 10 letras en la tuberia for( int i=0; i < 10; i+) c = alfabeto.charAt( (int) (Math. random()*26 ) ); tuberia.lanzar( ¢ ); // Inprime un registro con To afiadido System.out.printin( "“Lanzado "+c+" a la tuberia.” // Espera un poco antes de afadir mas letras try { sleepC Cint)(Math.random() * 100 ) 3; } catch( InterruptedException e ) { e.printStackTrace() ; } t 3 + CAPITULO 11: TAREAS Y MULTITAREA 221 Obsérvese que se crea una instancia de la clase Tuberia, y que se utiliza el “método iberia.lanzar() para que se vaya construyendo la tuberia, en principio de 10 ‘caracteres. Consumidor Ahora se reproduce el cddigo del consumidor, que extiende la clase Thread: class Consumidor extends Thread { private Tuberia tuberia; public Consumidor( Tuberia t ) { // Mantiene una copia propia del objeto compartido tuberia = t; t public void run© { char c; // Consume 10 letras de la tuberia for( int i-0; i < 10; i++) { = tuberia.recuperar(); // Imprime las letras retiradas System.out.printIn( "Recogido el caracter "+c ); // Espera un poco antes de capturar mas letras try { sleep( Cint)(Math.random() * 2000 ) ); } catch( InterruptedException e ) { e.printStackTrace() ; En este caso, como en el del productor, se cuenta con un método en la clase luberia, fuberia. recuperar(), para manejar la informacién. [productor y el consumidor. Los monitores, en general, son piezas muy importantes e las aplicaciones multitarea, porque mantienen el flujo de comunicacién entre las s Tuberia { private char buffer[] new char[6]; “private int siguiente = 0; // Flays para saber el estado del buffer vate boolean estaLlena = false; private boolean estaVacia - true; jj Método para retirar letras del buffer ‘public synchronized char recuperar() { J/ No se puede consumir si el buffer esta vacio nu 222 JAVA 2: MANUAL DE USUARIO Y TUTORIAL eR while( estaVacia try f waitQ; // Se sale cuando estaVacia cambia a false 3 catch( Interruptedexception e ) { e.printStackTraceQ ; A; j // Decrementa Ta cuenta, ya que va a consumir una Tetra siguiente--; // Comprueba si se retird la ditima letra if( siguiente — 0 ) estaVacia = true; // El buffer no puede estar Teno, porque acabamos de consumir estaLlena = false; notifyQ; // Devuelve 1a Tetra al thread consumidor return( buffer[siguiente] ); + true) { // Método para afiadir letras al buffer Public synchronized void lanzarc char c) { // Espera hasta que haya sitio para otra letra while€ estaLlena == true ) { try { waitQ; // Se sale cuando estatlena cambia a false J catch( InterruptedException e ) { e.printStackTraceQ; 3 // Afade una letra en el primer lugar disponible buffer[siguiente] = c; // Cambia al siguiente lugar disponible siguiente++; // Comprueba si el buffer esta Teno ifC siguiente == 6 ) estallena = true; estaVacia = false; notifyQ; En la clase Tuberia se pueden observar dos caracteristicas importantes: los miembros dato (buffer[]) son privados, y los métodos de acceso (lanzar() y recuperar()) son sincronizados. f Aqui se observa que la variable estaVacia es un semaforo, La naturaleza privada de los datos evita que el produetor y el consumidor accedan directamente a éstos. Sise permitiese el acceso directo de ambas tareas a los datos, se podrian produeit problemas; por ejemplo, si el consumidor intenta retirar datos de un buffer vaclo, obtendra excepciones innecesarias, o bien se bloqueard el proceso. Los métodos de acceso sincronizado impiden que los productores y consumidores: corrompan un objeto compartido: Mientras el productor esti afiadiendo una letra a la tuberia, el consumidor no la puede retirar, y viceversa. Esta sincronizacién es vital RAMA CAPITULO U1: TAREAS Y MULTITAREA 223 para mantener la integridad de cualquier objeto compartido. No seria lo mismo " sincronizar la clase en vez de los métodos, porque esto significaria que nadie puede acceder a las variables de la clase en paralelo, mientras que al sineronizar los métodos, sf pueden acceder a todas las variables que estan fuera de los métodos que pertenecen ala clase. Se pueden sincronizar incluso variables, para realizar alguna accion determinada sobre ellas, por ejemplo: sincronized( p ) f 7/ Aqui se colocaria el cédigo. Los threads que estén intentando // acceder a p se pararan y generaran una InterruptedException 3 El método notify) al final de cada método de acceso avisa a cualquier proceso que esté esperando por el objeto, ontonces el proceso que ha estado esperando intentard acceder de nuevo al objeto. En el método wait) se hace que la tarea se quede ala espera de que le Ilegue un notifi(), ya sea enviado por la tarea en ejecucién o por el sistema. Ahora que se dispone de un productor, un consumidor y un objeto compartido, se ‘necesita una aplicacion que arranque las tareas y que consiga que todos hablen con el mismo objeto que estan comparticndo. Esto es lo que hace el siguiente trozo de cidigo, del fuente Java1106. java: Class Javal106 { public static void main( String args[] ) { Tuberia t = new Tuberia(); Productor p = new Productor( t ); Consumidor c = new Consumidor¢ t ); p.startQO; c.startQ5 3 } Compilando y ejecutando esta aplicacién, se podra observar el modelo que se ha diseiado cn pleno funcionamicnto Monitorizacién del productor Los programas productor/consumidor a menudo emplean monitorizacion remota, que permite al consumidor observar la tarea del productor interaccionando con un usuario 0 con otra parte del sistema. Por ejemplo, en una red, un grupo de tareas “productores podrian trabajar cada uno en una estacién de trabajo. Los productores imprimirian documentos, almacenando una entrada en un registro (/og). Un consumidor (o miltiples consumidores) podria procesar el registro y realizar durante lanoche un informe de la actividad de impresion del dia anterior. 204 JAVA 2: MANUAL DE USUARIO Y TUTORIAL ORM Otro ejemplo a pequefia escala podria ser el uso de varias ventanas en una estacién de trabajo. Una ventana se puede usar para la entrada de informacién (el productor), y otra ventana reaccionaria a esa informacién (el consumidor). Peer es un observador general del sistema. UTILIDADES DE CONCURRENCIA El uso de synchronize y la pareja de métodos wait() y notifv(), no son suficientes para el control exhaustivo de la ejecucién de tareas. Por ello, desde la versién J2SE 5, Java dispone de una libreria de utilidades que proporcionan potentes construcciones de alto nivel para ejecutores, colas, temporizadores, bloqueos y otras primitivas de sincronizacién. Los semdforos, por ejemplo, pueden ser utilizados de la misma forma que el método wait() descrito anteriormente, pero para restringir el acceso a un bloque de codigo. Los semaforos son mas flexibles y pueden permitir el acceso concurrente de varias tareas, a la vez que permiten que las tareas puedan conocer su estado antes de pedir el bloqueo. La clase PoolRecurses proporciona un poo! de recursos, que aunque para efectos de simplicidad de la aplicacion, son simples objetos de tipo Integer, podria tratarse en aplicaciones reales de conexiones JDBC, por ejemplo. public class PoolRecursos { // Tamafio del pool de recursos private int tamPool; private Semaphore semaforo; // Creamos dos arrays. Uno que contiene los elementos que constituyen 7/ 10s recursos del pool y otro que indica si cada uno de esos // recursos esta siendo utilizado o no private Integer[] recursos; private boolean[] utilizado; // Constructor en el que creamos e] pool de recursos public PoolRecursos( int tamPool ) { this.tamPool = tamPool; // Creamos un semaforo para controlar el acceso a los recursos semaforo = new Semaphore( tamPool ); // Creamos el array de indicadores de uso de los recursos utilizado = new boolean[tamPoo1] ; // Creamos el array de recursos y le asignamos valores a cada uno recursos = new Integer[tamPoo1]; for( int i-0; 4 < tamPool; i++) recursos[i] = new IntegerCi 3 // Este método devuelve un recurso. Si todos los recursos estan // siendo utilizados, se producira el bloqueo hasta que alguno de // ellos se libere. Es un método sincronizado para que el codigo sea // totalmente “thread safe" DRAMA CAPITULO IL: TAREAS Y MULTITAREA public synchronized Integer adquiereRecurso© { try { // Solicitamos e1 semaforo semaforo.acquire(); } catch( Interruptedexception e ) { System.out.printin( e.getLocalizedMessageQ) ); 3 // Buscamos un recurso libre for( int i i < tamPool; i++ ) { if( utilizado[i] false ) { // Si 21 método no fuese sincronizado, varias tareas podrian // alcanzar este punto y se les devolveria el mismo recurso a // cada una de ellas utilizado[i] = true; return( recursos[i] ); £ return€ null ); 3 // Método utilizado para devolver un recurso al pool public void liberaRecurso( Integer recurso ) { // Utilizamos auto-unboxing utilizado[recurso] = false; semaforo.release() ; } 3 El constructor de la clase crea un seméforo indicando el nfimero de recursos disponibles, en el caso del ejemplo, dos. Cuando alguien necesita uno de los recursos, invoca al método adquiereRecursod) que llama al método de la clase Semaphore para adquirirlo. Si el contador del semaforo es menor que el utilizado en el constructor, el método continuara. Si todos Jos recursos estan en uso, el método bloqueara el acceso hasta que alguno de ellos quede disponible. Una de las caracteristicas mas tiles de los semaforos es que el método para adquirir el recurso y el método para devolverlo, pueden estar en la misma clase y ser accedidos concurrentemente por tareas distintas. La aplicacién Java1107 . java crea tres tareas que intentan acceder a los recursos del pool, utilizando un seméaforo para restringir el acceso a esos recursos. En la ejecucién se demuestra que una de las tres tareas siempre queda bloqueada cuando intenta acceder al recurso, porque solamente hay dos disponibles. Otro de los elementos que incorpora la libreria de concurrencia es la interfaz Executor, que permite iniciar la ejecucién de una tarea separada de su aplicacion, en Jugar de invocar al método s¢ari() de la clase Thread. Para crear una instancia de la clase ThreadPoolExecutor, que ¢s una de las implementaciones disponibles de la interfaz Executor, se utiliza la invocacién al 226 JAVA 2: MANUAL DE USUARIO Y TUTORIAL, ORAM método newFixedThreadPool(), indicando el ntimero de tareas que constituyen el pool. También se puede conseguir creando un objeto de tipo Queue para almacenar la informacién de las tareas ¢ invocar al constructor ThreadPoolExecutor. El primer método es mas simple y es el que se ha utilizado en el ejemplo Si solamente se quicre crear una tinica tarea, las implementaciones de Executor también deben proporcionar el métado newSingleThreadExecutor() para permitir esta posibilidad. La aplicacion Javal1108.java crea una clase que actia como un registro de eventos sobre un puerto determinado, presentando en pantalla los caracteres que recibe cuando se establece una conexién a ese puerto. Como es necesario controlar muchas conexiones simultaneamente que duran muy poco tiempo, es imprescindible tener una tarea separada para cada conexion. Como la creacién de objetos Thread es costosa para la Maquina Virtual Java, ol mejor modo de conseguirlo es utilizar un pool de tareas que pueda ser reutilizado por las nuevas conexiones cuando las anteriores vayan finalizando. Ejecutar la aplicacién en una ventana de comandos del sistema operative. En esa ventana aparecera un mensaje indicando que la aplicacién queda a la espera de que se establezean conexiones con ella. La clase Eeo es una versién simplificada de telnet, envia los caracteres que se tecleen a un socket. La secuencia de caracteres tecleada aparece en pantalla y se envia en el momento de pulsar la tecla Reforno. Ejecutar en una ventana de comandos diferente esta aplicacion, teclear una frase y enviarla pulsando la tecla Retorno. En la ventana de comandos apareceran los mensajes de conexién con el servidor, los correspondientes al uso de Ja aplicacion Eco y la frase tecleada. En la ventana de ejecucion del servidor Java1108 se mostrara la frase enviada por Eco. En una ventana de comandos diferente de las anteriores, volver a ejecutar la aplicacién Keo y repetir las acciones indicadas en el parrafo anterior. El resultado obtenido es semejante, porque el pool de conexiones definido en la clase Javal108 es dos. Sin embargo, las cosas no son iguales si se ejecuta de nuevo por tercera vez la aplicacién Eco en otra ventana de comandos diferente de las anteriores. Aparecerd el mensaje de que la conexion con el servidor se ha establecido, a pesar de que en la ventana del servidor no hay indicacién alguna de que dicha conexion se haya iniciado, Es més, si se teclea una frase en esta ventana y se envia al servidor pulsando Retorno, en la ventana del servidor no ocurre nada. Ahora, en una de las dos primeras ventanas de ejecucion de Eco, teclear SALIR y pulsar Reforno. La cjecucién concluiré y en la yentana del servidor se indicara dicha RAMA CAPITULO 11: TAREAS Y MULTITAREA _ 227 circunstancia, pero ademas, se iniciara la conexién con la aplicacién Eco que estaba detenida y se mostrara la frase que se habia introducido en la tercera ventana de Eco. En la aplicacién anterior se ha mostrado el uso de Exeeutor, que evita la utilizacién del método starf(), a favor de execute(). Otra interfaz que proporciona una alternativa, en este caso al método run(), es Callable. La interfaz Callable define el método call y proporciona una forma de obtener un resultado o capturar una excepeion desde una tarea separada. Los objetos Callable son enviados a un Executor, pero no ejecutados. El método submit() devuelve un objeto de tipo Future, que es quien proporciona el método get() para recuperar un resultado. A la hora de recuperar este resultado, si ya esta listo en la tea se devuelve, y en caso contrario, la tarea se bloquear4 hasta que esté disponible. Para mostrar el funcionamiento de Callable y Future, se crea la clase Callablelmp, que implementa la interfaz Callable y es capaz de devolver un objeto ¢etipo String cuando es invocada por una tarea. public class CallableImp implements Callable { /f Punto de entrada de la Tlamada que es invocado cuando un nuevo /{ hilo entra en ejecucién public String callQ { System.out. print In( "[2] Invocacion de cal1Q en 1a segunda tarea..” try { Thread.sleep( 500 ); } catch( InterruptedException e ) { System.out.printIn( e.getLocalizedMessageQ ); Ee System. out. printin( "[2] Completada 1a 1lamada a cal1Q en Ja segunda tarea.." ); return( "Concluido" ); } E La clase Javall09 utiliza la clase Callablefmp para simular acciones en una tarea separada mientras concluye las suyas propias. En este caso tan simple no se tealiza accion alguna, solamente se espera un lapso de tiempo invecando al método ‘sleep(). public class Javal109 { // Ejecucion de la prueba public void prueba() { f/f Utilizamos los métodos de utilidad de Executor para recuperar //-un objeto ExecutorService desde un hilo de ejecucién separado ExecutorService es = Executors.newSingleThreadExecutor(); System.out.printin( “[1] Iniciando prueba en la primera tarea.." // Ahora lanzamos Ta ejecucion de la segunda tarea, // correspondiente ala implementacién de Callable en este caso try { FuturecString> f = es.submit( new CallableImpQ ); // Fijamos el intervalo a 1000, para poder ver los efectos de la 228 JAVA 2: MANUAL DE USUARIO Y TUTORIAL, RAMA // sincronizacion Thread.sleep( 1000 ); System. out.printin¢ “[1] Primera tarea completada. Consultando a Future String salida = f.getQ; System.out.printIn(€ "El resultado desde Future es " +salida ); } catch( Interruptedexception e ) { System.out.printIn( e.getMessage() ); } catch( ExecutionException e ) { System.out.printfC "Error en 1a ejecucion de la prueba: %s", e.getLocalizedMessage() ); } // Cortamos 1a segunda tarea para que el programa concluya es. shutdown () 5 + public static void main( String[] args ) { Javall09 ej = new Javall09(); ej .prueba(); } Ejecutando la aplicacién, apareceran mensajes indicando lo que ocurre con las dos tareas en lo que respecta a su inicio y funcionamiento. El lector puede experimentat modificando los valores de sleep() en las dos tareas para que la principal se complete mas rapidamente, lo cual cambiaria el orden de los mensajes. Observe el lector que tanto en un caso como en otro, o en cualquier oita circunstancia, las dos tareas permanecerén siempre correctamente sincronizadas. Para finalizar esta seccién, indicar que Java, desde el J2SE 5, dispone de le interfaz BlockingQueue para cl control de las acciones entre tareas. En el ejemplo Javal110. java, se implementa un sistema de bitdcora, de registro 0 de log, basado en una Cola de este tipo para sincronizar la grabacién de mensajes procedentes de distintas tareas. El cédigo de la clase Logs que se reproduce a continuacion, toma como argumento en el constructor un objeto de tipo BlockingQueue para utilizarlo como almacén de los mensajes que las distintas tareas quieran registrar. Esia cola asegura que las operaciones son seguras entre todas las tareas que intenten afiadir elementos a Ia lista de mensajes. public class Logs implements Runnable { // Cola que se utilizara para pasar los mensajes entre dos tareas, // Va que genera los mensajes y ésta que los imprime private BlockingQueue cola; public Logs(BlockingQueue

También podría gustarte