Tutorial de Java – Creació i Control de Fils

Abans d’entrar en més profunditats en els fils d’execució, es proposa una referència ràpida de la classe Thread.

La classe thread

És la classe que encapsula tot el control necessari sobre els fils d’execució (threads). Cal distingir clarament un objecte Thread d’un fil d’execució o thread. Aquesta distinció resulta complicada, tot i que es pot simplificar si es considera a l’objecte Thread com el panell de control d’un fil d’execució (thread). La classe Thread és l’única manera de controlar el comportament dels fils i per a això se serveix dels mètodes que s’exposen en les seccions següents.

Mètodes de Classe

Aquests són els mètodes estàtics que han de cridar-se de manera directa en la classe thread.

currentThread ()

Aquest mètode retorna l’objecte thread que representa el fil d’execució que s’està executant actualment.

yield ()

Aquest mètode fa que l’intèrpret canviï de context entre el fil actual i el següent fil executable disponible. És una manera d’assegurar que ens fils de menor prioritat no pateixin inanició.

sleep (long)

El mètode sleep () fa que l’intèrpret posi a el fil en curs a dormir durant el nombre de milisegons que s’indiquin en el paràmetre d’invocació. Un cop transcorreguts aquests milisegons, dit fil tornarà a estar disponible per a la seva execució. Els rellotges associats a la major part dels intèrprets de Java no seran capaços d’obtenir precisions majors de 10 mil·lisegons, per molt que es permeti indicar fins a nanosegons en l’anomenada alternativa a aquest mètode.

Mètodes d’Instància

Aquí no estan recollits tots els mètodes de la classe Thread, sinó només els més interessants, perquè els altres corresponen a àrees on l’estàndard de Java no està complet, i pot ser que es quedin obsolets en la propera versió de l’JDK, per això, si es vol completar la informació que aquí s’exposa s’ha de recórrer a la documentació de la interfície de programació d’aplicació (API) de l’JDK.

start ()

Aquest mètode indica a l’intèrpret de Java que creï un context de el fil de el sistema i comenci a executar-lo. A continuació, el mètode run () d’aquest fil serà invocat en el nou context de el fil. Cal tenir precaució de no cridar a mètode start () més d’una vegada sobre un fil determinat.

run ()

El mètode run () constitueix el cos d’un fil a execució. Aquest és l’únic mètode de la interfície runnable. És cridat pel mètode start () després que el fil apropiat de sistema s’hagi inicialitzat. Sempre que el mètode run () retorni el control, el fil actual s’aturarà.

stop ()

Aquest mètode provoca que el fil s’aturi de manera immediata. Sovint constitueix una manera brusca d’aturar un fil, especialment si aquest mètode s’executa sobre el fil en curs. En aquest cas, la línia immediatament posterior a la crida a mètode stop () no arriba a executar-se mai, ja que el context de el fil mor abans que stop () retorni el control. Una forma més elegant de detenir un fil és utilitzar alguna variable que ocasioni que el mètode run () acabi de manera ordenada. En realitat, mai s’hauria de recórrer a l’ús d’aquest mètode.

suspend ()

El mètode suspend () és diferent de stop (). suspend () pren el fil i provoca que s’aturi la seva execució sense destruir el fil de sistema subjacent, ni l’estat de el fil anteriorment en execució. Si l’execució d’un fil es suspèn, pot cridar-se a resumeix () sobre el mateix fil per aconseguir que torni a executar-se de nou.

resumeix ()

El mètode resumeix () s’utilitza per reviure un fil suspès. No hi ha garanties que el fil comenci a executar-se immediatament, ja que pot haver-hi un fil de major prioritat en execució actualment, però resumeix () ocasiona que el fil torni a ser un candidat a ser executat.

setPriority (int)

el mètode setPriority () assigna a el fil la prioritat indicada pel valor passat com a paràmetre. Hi ha força constants predefinides per a la prioritat, definides en la classe Thread, com ara MIN_PRIORITY, NORM_PRIORITY i MAX_PRIORITY, que prenen els valors 1, 5 i 10, respectivament. Com a guia aproximada d’utilització, es pot establir que la major part dels processos a nivell d’usuari haurien de prendre una prioritat al voltant de NORM_PRIORITY. Les tasques en segon pla, com una entrada / sortida a xarxa o el nou dibuix de la pantalla, haurien de tenir una prioritat propera a MIN_PRIORITY. Amb les tasques a les que es fixi la màxima prioritat, al voltant de MAX_PRIORITY, cal ser especialment curosos, perquè si no es fan crides a sleep () o yield (), es pot provocar que l’intèrpret Java quedi totalment fora de control .

getPriority ()

Aquest mètode retorna la prioritat de el fil d’execució en curs, que és un valor comprès entre un i deu.

setName (String)

Aquest mètode permet identificar el fil amb un nom menmónico. D’aquesta manera es facilita la depuració de programes multifil. El nom mnemònic apareixerà en totes les línies de traçat que es mostren cada vegada que l’intèrpret Java imprimeix excepcions no capturades.

getName ()

Aquest mètode retorna el valor actual, de tipus cadena, assignat com a nom a el fil en execució mitjançant setName ().

Creació d’un Thread

Hi ha dues maneres d’aconseguir fils d’execució (threads) en Java. Una és implementant la interfície runnable, l’altra és estendre la classe Thread.

La implementació de la interfície runnable és la forma habitual de crear fils. Els interfícies proporcionen a l’programador una forma d’agrupar el treball d’infraestructura d’una classe. S’utilitzen per dissenyar els requeriments comuns a el conjunt de classes a implementar. La interfície defineix el treball i la classe, o classes, que implementen la interfície per realitzar aquest treball. Els diferents grups de classes que implementin la interfície hauran de seguir les mateixes regles de funcionament.

Hi ha una quantes diferències entre interfície i classe, que ja són conegudes i aquí només es resumeixen. Primer, una interfície només pot contenir mètodes abstractes i / o variables estàtiques i finals (constants). Les classes, d’altra banda, poden implementar mètodes i contenir variables que no siguin constants. Segon, una interfície no pot implementar qualsevol mètode. Una classe que implementi una interfície ha d’implementar tots els mètodes definits en aquest interfície. Un interfície té la possibilitat de poder estendre d’altres interfícies i, a canvi que les classes, pot estendre de múltiples interfícies. A més, un interfície no pot ser instanciat amb l’operador new; per exemple, la següent sentència no està permesa:

Runnable a = new Runnable(); // No se permite

El primer mètode de crear un fil d’execució és simplement estendre la classe Thread:

class MiThread extends Thread { public void run() { . . . }

l’exemple anterior crea una nova classe MiThread que estén la classe Thread i sobreescriu el mètode Thread.run () per la seva pròpia implementació. El mètode run () és on es realitzarà tota la feina de la classe. Estenent la classe Thread, es poden heretar els mètodes i variables de la classe pare. En aquest cas, només es pot estendre o derivar un cop de la classe pare. Aquesta limitació de Java pot ser superada a través de la implementació de runnable:

public class MiThread implements Runnable { Thread t; public void run() { // Ejecución del thread una vez creado } }

En aquest cas necessitem crear una instància de Thread abans que el sistema pugui executar el procés com un fil. A més, el mètode abstracte run () està definit en la interfície runnable i ha de ser implementat. L’única diferència entre els dos mètodes és que aquest últim és molt més flexible. En l’exemple anterior, encara hi ha la oportunitat d’estendre la classe MiThread, si fos necessari. La majoria de les classes creades que necessiten executar-se com un fil, implementaran la interfície runnable, ja que probablement s’estendran alguna de la seva funcionalitat a altres classes.

No pensar que la interfície runnable està fent alguna cosa quan la tasca s’està executant. Només conté mètodes abstractes, amb la qual cosa és una classe per donar idea sobre el disseny de la classe Thread. De fet, si s’observen els fonts de Java, es pot comprovar que només conté un mètode abstracte:

package java.lang;public interface Runnable { public abstract void run() ; }

I això és tot el que hi ha sobre la interfície runnable. Com es veu, una interfície només proporciona un disseny per a les classes que hagin de ser implementades. En el cas de runnable, força a la definició de mètode run (), per tant, la major part de la feina es fa a la classe Thread. Un cop d’ull una mica més profund a la definició de la classe Thread dóna idea del que realment està passant:

public class Thread implements Runnable { ... public void run() { if( tarea != null ) tarea.run() ; } } ... }

D’aquesta trosset de codi es desprèn que la classe Thread també implementi la interfície runnable. tarea.run () s’assegura que la classe amb què treballa (la classe que va a executar-se com un fil) no sigui nul·la i executa el mètode run () d’aquesta classe. Quan això passi, el mètode run () de la classe farà que corri com un fil.

A continuació es presenta un exemple, java1001.java, que implementa la interfície runnable per crear un programa multifil.

class java1001 { static public void main( String args ) { // Se instancian dos nuevos objetos Thread Thread hiloA = new Thread( new MiHilo(),"hiloA" ); Thread hiloB = new Thread( new MiHilo(),"hiloB" ); // Se arrancan los dos hilos, para que comiencen su ejecución hiloA.start(); hiloB.start(); // Aquí se retrasa la ejecución un segundo y se captura la // posible excepción que genera el método, aunque no se hace // nada en el caso de que se produzca try { Thread.currentThread().sleep( 1000 ); }catch( InterruptedException e ){} // Presenta información acerca del Thread o hilo principal // del programa System.out.println( Thread.currentThread() ); // Se detiene la ejecución de los dos hilos hiloA.stop(); hiloB.stop(); } }class NoHaceNada {// Esta clase existe solamente para que sea heredada por la clase// MiHilo, para evitar que esta clase sea capaz de heredar la clase// Thread, y se pueda implementar el interfaz Runnable en su// lugar}class MiHilo extends NoHaceNada implements Runnable { public void run() { // Presenta en pantalla información sobre este hilo en particular System.out.println( Thread.currentThread() ); } }

Com es pot observar, el programa defineix una classe MiHilo que estén a la classe NoHaceNada i implementa la interfície runnable. Es redefineix el mètode run () a la classe MiHilo per presentar informació sobre el fil.

L’única raó d’estendre la classe NoHaceNada és proporcionar un exemple de situació en què calgui estendre alguna altra classe, a més de implementar la interfície.

En l’exemple java1002.java mostra el mateix programa bàsicament, però en aquest cas extenent la classe Thread, en lloc d’implementar la interfície runnable per crear el programa multifil.

class java1002 { static public void main( String args ) { // Se instancian dos nuevos objetos Thread Thread hiloA = new Thread( new MiHilo(),"hiloA" ); Thread hiloB = new Thread( new MiHilo(),"hiloB" ); // Se arrancan los dos hilos, para que comiencen su ejecución hiloA.start(); hiloB.start(); // Aquí se retrasa la ejecución un segundo y se captura la // posible excepción que genera el método, aunque no se hace // nada en el caso de que se produzca try { Thread.currentThread().sleep( 1000 ); }catch( InterruptedException e ){} // Presenta información acerca del Thread o hilo principal // del programa System.out.println( Thread.currentThread() ); // Se detiene la ejecución de los dos hilos hiloA.stop(); hiloB.stop(); } }class MiHilo extends Thread { public void run() { // Presenta en pantalla información sobre este hilo en particular System.out.println( Thread.currentThread() ); } }

En aquest cas, la nova classe MiHilo estén la classe Thread i no implementa la interfície runnable directament (la classe Thread implementa la interfície runnable, per la qual cosa indirectament MiHilo també està implementant aquest interfície). La resta de el programa és similar a l’anterior.

I encara es pot presentar un exemple més simple, utilitzant un constructor de la classe Thread que no necessita paràmetres, tal com es presenta en l’exemple java1003.java. En els exemples anteriors, el constructor utilitzat per Thread necessitava dos paràmetres, el primer un objecte de qualsevol classe que implementi la interfície runnable i el segon una cadena que indica el nom de l’fil (aquest nom és independent de el nom de la variable que referència a l’ objecte Thread).

class java1003 { static public void main( String args ) { // Se instancian dos nuevos objetos Thread Thread hiloA = new MiHilo(); Thread hiloB = new MiHilo(); // Se arrancan los dos hilos, para que comiencen su ejecución hiloA.start(); hiloB.start(); // Aquí se retrasa la ejecución un segundo y se captura la // posible excepción que genera el método, aunque no se hace // nada en el caso de que se produzca try { Thread.currentThread().sleep( 1000 ); }catch( InterruptedException e ){} // Presenta información acerca del Thread o hilo principal // del programa System.out.println( Thread.currentThread() ); // Se detiene la ejecución de los dos hilos hiloA.stop(); hiloB.stop(); } }class MiHilo extends Thread { public void run() { // Presenta en pantalla información sobre este hilo en particular System.out.println( Thread.currentThread() ); } }

Les sentències en aquest exemple per instanciar objectes Thread, són molt menys complexes, sent el programa, en essència, el mateix dels exemples anteriors.

Arrencada d’un Thread

Les aplicacions s’executen main () després d’arrencar. Aquesta és la raó que main () sigui el lloc natural per crear i arrencar altres fils. La línia de codi:

 t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) );

crea un nou fil d’execució. Els dos arguments passats representen el nom de el fil i el temps que es desitja que esperi abans d’imprimir el missatge.

A l’tenir control directe sobre els fils, cal arrencar-los explícitament. En l’exemple amb:

 t1.start();

start (), en realitat és un mètode ocult en el fil d’execució que crida a run ().

Manipulació d’un thread

Si tot va anar bé en la creació d’el fil, t1 hauria de contenir un thread vàlid, que controlarem en el mètode run ().

Un cop dins de run (), es poden començar les sentències d’execució com en altres programes. run () serveix com a rutina main () per als fils; quan run () acaba, també ho fa el fil. Tot el que es vulgui que faci el fil d’execució ha d’estar dins de run (), per això quan es diu que un mètode és runnable, és obligatori escriure un mètode run ().

En aquest exemple , s’intenta immediatament esperar durant una quantitat de temps aleatòria (passada a través de l’constructor):

sleep( retardo );

el mètode sleep () simplement li diu a el fil d’execució que dormi durant els mil·lisegons especificats. S’hauria utilitzar sleep () quan es pretengui retardar l’execució de l’fil. sleep () no consumeix recursos de sistema mentre el fil dorm. D’aquesta manera altres fils poden seguir funcionant. Un cop fet el retard, s’imprimeix el missatge “Hola Món!” amb el nom de el fil i el retard.

Suspensió d’un Thread

Pot resultar útil suspendre l’execució d’un fil sense marcar un límit de temps. Si, per exemple, està construint un applet amb un fil d’animació, segurament es voldrà permetre a l’usuari l’opció d’aturar l’animació fins que vulgui continuar. No es tracta d’acabar l’animació, sinó desactivar-la. Per a aquest tipus de control dels fils d’execució es pot utilitzar el mètode suspend ().

 t1.suspend();

Aquest mètode no atura l’execució permanentment. El fil és suspès indefinidament i per tornar a activar-lo de nou es necessita realitzar una invocació a mètode resumeix ():

 t1.resume();

Aturada d’un Thread

l’últim element de control que es necessita sobre els fils d’execució és el mètode stop (). S’utilitza per acabar l’execució d’un fil:

 t1.stop();

Aquesta crida no destrueix el fil, sinó que deté la seva execució. L’execució no es pot reprendre ja amb t1.start (). Quan es desasignen les variables que s’usen en el fil, l’objecte actiu (creat amb new) quedarà marcat per eliminar-lo i el garbage collector s’encarregarà d’alliberar la memòria que utilitzava.

En l’exemple, no es necessita aturar explícitament el fil d’execució. Simplement se li deixa acabar. Els programes més complexos necessitaran un control sobre cada un dels fils que llancin, el mètode stop () pot utilitzar-se en aquestes situacions.

Si es necessita, es pot comprovar si un fil és viu o no; considerant viu un fil que ha començat i no ha estat detingut.

 t1.isAlive();

Aquest mètode retornarà true en cas que el fil t1 estigui viu, és a dir, ja s’hagi cridat al seu mètode run () i no hagi estat parat amb un stop () ni hagi acabat el mètode run () en la seva execució.

En l’exemple no hi ha problemes de realitzar una parada incondicional, a l’estar tots els fils vius. Però si a un fil d’execució, que pot no estar viu, se li invoca el seu mètode stop (), es generarà una excepció.En aquest cas , en els que l’estat de el fil no es pot conèixer per endavant és on es requereix l’ús de l’ mètode isAlive ( ) .

Leave a Comment

L'adreça electrònica no es publicarà. Els camps necessaris estan marcats amb *