Fil: Un fil (thread en anglés) és la unitat més xicoteta d'instruccions programades
que són gestionades pel sistema operatiu. La implementació de fils i processos és diferent
per cada sistema operatiu, però en la majoria dels casos, un fil és una part d'un procés.
Els fils són gestionats pel sistema operatiu, però més específicament pel planificador de tasques (scheduler).
Els distints fils d'un procés es poden executar de manera concurrent, compartint recursos del procés (com la memòria),
cosa que no ocorre entre diferents processos.
Avantatges de la multitasca davant de la multiprogramació¶
Major capacitat de resposta: pel fet que hi pot haver un fil atenent peticions mentre un altre
realitza una altra tasca més llarga. S'usa a la programació de serveis als servidors, un fil
s'encarrega de rebre peticions i per cada petició s'obre un altre fil per atendre-la.
Compartició de recursos, ja que tots els fils d'un procés tenen accés als recursos del procés,
per la qual cosa no cal cap mecanisme addicional. El que s'haurà d'evitar són els problemes
derivats que diversos fils accedeixin alhora a un recurs, per exemple, la memòria, així caldrà
prestar més atenció a la sincronització entre fils.
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. Parlant d'ús de memòria i recursos és més barat crear fils que processos.
En sistemes amb diversos nuclis reals s'aconsegueix un paral·lelisme real en l'execució de fils.
Un fils és molt paregut a un process, per tant, necessiten pràcticament les mateixes dades per funcionar.
L'única diferencia és que hi ha alguns recursos que estan compartits entre tots els fils d'un procés,
i d'altres, que cada fil té el seu.
Cada fil té el seu propi comprador de programa, seus registres i la seua pila. Dins d'un mateix procés,
tots els fils comparteixen el codi font, les dades i el recursos.
Els fils, com els processos, poden canviar d'estat durant l'execució.
El comportament dependrà de l'estat:
Nou: el fil es troba preparat per ser executat però encara no s'ha cridat. Els fils s'inicien
en la creació del procés, però no comencen fins que el procés 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ó.
Simplificant, 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ó 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. Poden acabar per si mateix o perquè el procés que el 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 senzillesa només utilitzen un fil d'execució, mentre que altres programes
més complexos utilitzen diversos fils d'execució. Als fils se'ls anomena processos lleugers, en
contraposició als processos anomenats processos pesats.
Un exemple de la programació multifil és el d'un programari que utilitza una interfície gràfica.
Un fil s'encarrega de gestionar la interfície gràfica i les peticions de l'usuari, i els altres fils
s'encarreguen de realitzar les tasques en el background. Si no es fa d'aquesta manera,
la interfície gràfica es queda bloquejada fins que acabe la tasca.
A 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. En aquest fil d'execució,
es poden crear nous fils per executar diferents tasques. Els nous fils no cal que executen el mateix codi
que el fil principal.
A Java, per crear un nou fil cal crear un nou objecte, que ha de complir algun dels següents requisits:
Estendre la classe Thread:
publicclassMyThreadextendsThread{publicvoidrun(){// Codi del fil}}
Implementant la interfície Runnable:
publicclassMyRunnableimplementsRunnable{publicvoidrun(){// Codi del fil}}
En qualsevol de les dues opcions, caldrà crear una classe nova.
Aquesta classe ha d'implementar el mètode public void run(),
que contindrà el codi que executarà el fil.
A més, caldrà crear una instància de la classe Thread per poder
executar el fil.
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);}}}
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 fil que ha creat un altre fil secundari, pot decidir acabar amb l'execució del segon.
Aquesta acció s'anomena interrompre l'execució d'un fil i es pot portar a terme
mitjançant el mètode Thread interrupt(),
que acaba la seua execució.
Si el fil interromput està en un mètode sleep o join, es llançarà l'excepció InterruptedException.
Interrompre l'execució d'un fil
El fil InterruptedThread saluda a l'usuari 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 a què el fil pare espere que el fil fill finalitze.
Per fer-ho utilitzarem el mètode Thread join(long millis),
que espera a que aquest Thread acabe per continuar.
El paràmetre long millis indica el temps màxim per esperar que el fill acabe.
Si supera aquest temps, el fil que estava esperant continuarà la seua execució.
Si s'invoca amb un 0 o sense paràmetre, esperarà indefinidament.
Aquest mètode llança una excepció InterruptedException, que cal gestionar.
Threadt1=newSleepThread(5000);try{t1.start();// Llança el filt1.join();// El fil principal esperarà a que acabe el fil f1System.out.println("El fil1 ha acabat.");}catch(InterruptedExceptione){System.out.println(t1.getName()+" interromput.");}
Esperar a 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.
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.");}}