Un fil (thread en anglés) és la unitat més xicoteta d'instruccions programades que
gestiona el sistema operatiu. La implementació de fils i processos és diferent en cada sistema
operatiu, però en la majoria dels casos un fil és una part d'un procés.
Figura 1. Procés amb dos fils
Els fils els gestiona el sistema operatiu, més específicament el planificador de tasques
(scheduler). Els diferents fils d'un procés es poden executar de manera concurrent i
compartir recursos del procés, com la memòria. Això no ocorre entre processos diferents.
Avantatges de la multitasca davant de la multiprogramació¶
Treballar amb fils en lloc de processos independents aporta diversos avantatges:
Major capacitat de resposta: un fil pot atendre peticions mentre un altre realitza una
tasca més llarga. En la programació de serveis, per exemple, un fil s'encarrega de rebre
peticions i, per cada petició, s'obri un altre fil per atendre-la.
Compartició de recursos: tots els fils d'un procés tenen accés als recursos del procés,
per la qual cosa no cal cap mecanisme addicional. No obstant això, cal evitar els problemes
que genera que diversos fils accedisquen alhora a un mateix recurs (per exemple, la memòria)
i, per tant, cal prestar més atenció a la sincronització entre fils.
Menor cost de creació: com que tots els fils d'un procés utilitzen el mateix espai de
memòria, per crear nous fils no cal reservar memòria. En ús de memòria i recursos, crear
fils és més barat que crear processos.
Paral·lelisme real: en sistemes amb diversos nuclis reals, els fils s'executen de manera
realment paral·lela.
Un fil és molt paregut a un procés i, per tant, necessita pràcticament les mateixes dades per
funcionar. L'única diferència és que alguns recursos estan compartits entre tots els fils d'un
procés, mentre que altres són propis de cada fil.
Cada fil té el seu propi comptador de programa, els seus registres i la seua pila. Dins d'un
mateix procés, tots els fils comparteixen el codi font, les dades i els recursos.
Els fils, com els processos, poden canviar d'estat durant l'execució. El comportament depén de
l'estat en què es troben:
Nou: el fil està preparat per ser executat, però encara no s'ha iniciat. Els fils es
creen amb el procés, però no comencen fins que aquest ho indique.
A punt: el fil no s'està executant, però està preparat per fer-ho.
Executable (runnable): el fil està preparat per executar-se o fins i tot en execució.
No es pot saber si s'està executant o no, perquè el maquinari no informa d'aquesta situació.
Per simplificar, es considera que tots els fils d'un procés s'executen de manera paral·lela.
Bloquejat: el fil està bloquejat, per exemple, esperant una operació d'E/S o una
sincronització.
Acabat: el fil ha finalitzat, però no ha alliberat recursos, ja que no li pertanyen a
ell, sinó al procés que l'ha creat. Pot acabar per si mateix o perquè el procés que l'ha
creat el finalitza.
De manera general, un fil és un procés que s'està executant en un moment determinat a la CPU.
Hi ha programes que, per la seua senzillesa, només utilitzen un fil d'execució, mentre que
altres, més complexos, en utilitzen diversos. Els fils s'anomenen processos lleugers, en
contraposició als processos, anomenats processos pesats.
Un exemple de programació multifil és un programari amb interfície gràfica. Un fil s'encarrega
de gestionar la interfície i les peticions de la persona usuària, i la resta de fils realitzen
les tasques en segon pla (background). Si no es fa d'aquesta manera, la interfície gràfica es
queda bloquejada fins que acabe la tasca.
En Java, els fils es gestionen mitjançant la classe
Thread.
Quan un programa s'executa, es crea un procés que té un fil d'execució principal. Des d'aquest
fil es poden crear nous fils per executar diferents tasques, i no cal que executen el mateix
codi que el fil principal.
En Java, per crear un nou fil cal crear un objecte que complisca algun dels requisits
següents:
Estendre la classe Thread:
publicclassMyThreadextendsThread{publicvoidrun(){// Codi del fil}}
Implementar la interfície Runnable:
publicclassMyRunnableimplementsRunnable{publicvoidrun(){// Codi del fil}}
En qualsevol de les dues opcions, cal crear una classe nova que implemente el mètode
public void run(), que conté el codi que executarà el fil. A més, cal crear una instància de
la classe Thread per poder executar el fil.
Exemple: Creació d'un fil implementant Runnable
La classe HelloRunnable implementa la interfície Runnable i defineix el mètode run
amb el codi que s'executarà en el fil.
HelloRunnable.java
packageud2.examples.runnable;importjava.util.concurrent.ThreadLocalRandom;classHelloRunnableimplementsRunnable{publicvoidrun(){for(inti=0;i<5;i++){intsleepTime=ThreadLocalRandom.current().nextInt(500,1000);try{Thread.sleep(sleepTime);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.printf("El fil %s et saluda per %d vegada.\n",Thread.currentThread().getName(),i);}}}
Després, la classe StartHelloRunnables crea diferents instàncies de Thread amb
HelloRunnable i inicia els fils amb el mètode start.
StartHelloRunnables.java
packageud2.examples.runnable;importjava.util.concurrent.ThreadLocalRandom;publicclassStartHelloRunnables{publicstaticvoidmain(String[]args)throwsInterruptedException{HelloRunnablerc=newHelloRunnable();Threadthread1=newThread(rc);thread1.setName("Fil1");Threadthread2=newThread(rc);thread2.setName("Fil2");Threadthread3=newThread(rc);thread3.setName("Fil3");thread1.start();thread2.start();thread3.start();for(inti=0;i<5;i++){intsleepTime=ThreadLocalRandom.current().nextInt(500,1000);Thread.sleep(sleepTime);System.out.printf("El fil principal et saluda per %d vegada.\n",i);}}}
Exemple: Creació d'un fil estenent Thread
La classe HelloThread estén la classe Thread i sobreescriu el mètode run amb el codi
que s'executarà en el fil.
HelloThread.java
packageud2.examples.thread;importjava.util.concurrent.ThreadLocalRandom;classHelloThreadextendsThread{publicHelloThread(Stringname){super(name);}@Overridepublicvoidrun(){try{for(inti=0;i<5;i++){intsleepTime=ThreadLocalRandom.current().nextInt(500,1000);Thread.sleep(sleepTime);System.out.printf("El fil %s et saluda per %d vegada.\n",Thread.currentThread().getName(),i);}}catch(InterruptedExceptione){System.out.printf("El fil %s ha segut interromput\n",Thread.currentThread().getName());}}}
La classe StartHelloThreads crea diferents instàncies de HelloThread i les inicia amb
el mètode start.
StartHelloThreads.java
packageud2.examples.thread;importjava.util.concurrent.ThreadLocalRandom;publicclassStartHelloThreads{publicstaticvoidmain(String[]args){HelloThreadthread1=newHelloThread("Fil1");HelloThreadthread2=newHelloThread("Fil2");HelloThreadthread3=newHelloThread("Fil3");thread1.start();thread2.start();thread3.start();try{for(inti=0;i<5;i++){intsleepTime=ThreadLocalRandom.current().nextInt(500,1000);Thread.sleep(sleepTime);System.out.printf("El fil principal et saluda per %d vegada.\n",i);}}catch(InterruptedExceptione){System.out.println("El fil principal ha segut interromput.");}}}
El mètode estàtic
Thread.sleep(long millis)
permet parar l'execució d'un fil durant un temps determinat (també el fil principal). Aquest
mètode llança una excepció InterruptedException, que cal gestionar.
El fil que ha creat un fil secundari pot decidir acabar amb l'execució d'aquest. Aquesta acció
s'anomena interrompre l'execució d'un fil i es porta a terme amb el mètode
Thread.interrupt().
Si el fil interromput està en un mètode sleep o join, es llança l'excepció
InterruptedException.
Exemple: Interrompre l'execució d'un fil
El fil InterruptedThread saluda la persona usuària cada dècima de segon.
InterruptedThread.java
packageud2.examples.interrupt;classInterruptedThreadextendsThread{publicInterruptedThread(Stringname){super(name);}@Overridepublicvoidrun(){try{for(inti=0;i<1000;i++){System.out.printf("El fil %s et saluda per %d vegada.\n",Thread.currentThread().getName(),i);Thread.sleep(100);}}catch(InterruptedExceptione){System.out.println(Thread.currentThread().getName()+" interromput.");}}}
El programa principal crea tres instàncies de InterruptedThread i les inicia. Després,
cada mig segon interromp un dels fils.
Igual que amb els processos, es pot forçar que el fil pare espere que el fil fill finalitze.
Per fer-ho, s'utilitza el mètode
Thread.join(long millis),
que espera que el Thread acabe per continuar.
El paràmetre long millis indica el temps màxim d'espera. Si se supera aquest temps, el fil que
estava esperant continua la seua execució. Si s'invoca amb un 0 o sense paràmetre, espera
indefinidament. Aquest mètode també llança una excepció InterruptedException, que cal gestionar.
Threadt1=newSleepThread(5000);try{t1.start();// Llança el filt1.join();// El fil principal espera que acabe el fil t1System.out.println("El fil1 ha acabat.");}catch(InterruptedExceptione){System.out.println(t1.getName()+" interromput.");}
Exemple: Esperar que un fil acabe
El fil SleepThread espera els mil·lisegons que se li passen com a paràmetre.
SleepThread.java
packageud2.examples.join;publicclassSleepThreadextendsThread{privatefinalintmilliseconds;publicSleepThread(Stringname,intmilliseconds){super(name);this.milliseconds=milliseconds;}@Overridepublicvoidrun(){try{System.out.printf("El fil %s dormint durant %.2f segons.\n",this.getName(),milliseconds/1000.0);Thread.sleep(milliseconds);}catch(InterruptedExceptione){System.out.println(Thread.currentThread().getName()+" interromput.");}}}
El programa principal crea tres instàncies de SleepThread i, en ordre, va iniciant els
fils, un després de l'altre.
StartSleepThreads.java
packageud2.examples.join;importjava.util.List;publicclassStartSleepThreads{publicstaticvoidmain(String[]args){List<SleepThread>threads=List.of(newSleepThread("Fil 1",2000),newSleepThread("Fil 2",1000),newSleepThread("Fil 3",500));try{for(SleepThreadthread:threads){thread.start();System.out.printf("El fil %s ha començat.\n",thread.getName());thread.join();System.out.printf("El fil %s ha acabat.\n",thread.getName());}}catch(InterruptedExceptione){System.out.println(Thread.currentThread().getName()+" interromput.");}System.out.println("Tots els fils han acabat.");}}
📌 Aquest document pot quedar desactualitzat després d’imprimir-lo. Pots consultar la versió més recent
a la pàgina web.
🌿 Abans d’imprimir aquest document, considera si és realment necessari. Redueix el consum de paper i ajuda a protegir el nostre entorn.