On this page
Thread
15809
Els threads permeten executar seccions de codi en paral.lel (a la vegada) enlloc de sequencialment (un després de l’altre).
Tots els programes tenen almenys un thread quan s’executen, que s’anomena “main”, i és el responsable d’executar la funció main().
També hi ha altres threads que s’executen per defecte en una JVM, com pot ser el “garbage collector”.
Thread
Un thread és una seqüència d’instruccions que s’executen de manera separada del reste del progrmama.
Cada thread està representat per un objecte: una instància de la classe java.lang.Thread (o d’una subclasse).
Si un programa crea un o més threads a partir del thread “main” parlem d’un progrmama multi-thread.
La classe Thread té un mètode estàtic anomenat currentThread que et permet obtenir una referència a l’objecte thread que s’està executant actualment:
val thread: Thread La classe Thread emmagatzema informació bàsica sobre el thread: el seu nom, identificador (long), prioritat i algunes altres característiques que es poden obtenir mitjançant els mètodes de la classe Thread.
La informació del thread principal
A continuació fem servir el thread main com exemple per obtenir les característiques fent-hi referències a través d’un objecte de la classe Thread.
fun val thread: Thread
}Totes les declaracions d’aquest programa són executades pel thread principal.
Podeu veure la informació general sobre aquest fil:
-
thread.nameretorna el nom del thread. -
thread.idretorna l’identificador únic del thread. -
thread.isAliveens indica si el thread s’ha iniciat i encara no ha mort. -
thread.priorityretorna la prioritat del thread. Cada thread té una prioritat que determina l’ordre d’execució: els threads amb una prioritat més alta s’executen abans que els threads amb prioritats més baixes. -
t.isDaemoncomprova si el thread és un dimoni. Un “daemon thread” (prové de la terminologia UNIX) és un thread de baixa prioritat que s’executa en segon pla per realitzar tasques com la “garbage collection”, etc. La JVM no espera que els “daemon” threads s’aturin abans de sortir, mentre que si que espera als thread que no són dimoni per finalitzar l’execució del programa.
La sortida del programa:
Name: main
ID: 1
Alive: true
Priority: 5
Daemon: falseCada característica es pot canviar configurant un valor nou:
val thread: Thread
t.name // New name: helloEl mateix codi es pot aplicar a qualsevol thread en execució, no només al principal.
Activitat
1.- Quants threads pot tenir un programa?
- Almenys un
- D’un al nombre de nuclis de processador
- Exactament un
- Nombre exacte de nuclis de processador
{% sol %} Almenys un. {% endsol %}
TODO
Custom threads
16006
A partir del thread principal (main) pots crear nous threads.
Per fer-ho, has de crear nous objectes threads, escriue codi per executar-lo en un thread separat i iniciar-lo.
Crear un thread
Pots crear un thread de dos maneres diferents:
-
Estendre la classe
Threadi sobreesciure el mètoderun. -
Implementar la interfície
Runnablei passar la implementació al constructor de la classeThread.
A continuació tens un exemple en que estenem la classe Thread:
: override fun val msg
}
}I aquí tens un exemple implementat la interfície Runnable:
: Runnable override fun val thread val msg
}
}Pots veure que en tots dos casos has de sobreescriure el mètode run amb el codi que vols que executi el thread.
Has de fer servir una opció o altre en funció de la tasca i de les teves preferències: si estens la classe Thread pots acceptar atributs i mètodes de la classe base, però no pots estendre altres classes.
La classe Thread té molts constructors: podeu trobar una llista completa en aquest document: Thread - Constructor Summary.
Si estenem la classe Thread podem crear un objecte thread directamentmitjançant el contructor de la subclasse:
val t1 En canvi, si implementem la interfície Runnable hem de crear un objecte mitjançant un dels constructors de la classe Thread:
val t2 Implementant la interfície Runnable podem especificar el nom del thread passant-lo al constructor:
val helloThread Pots veure que la intefície Runnable t’ofereix una manera més versàtil de treballar amb threads ja que no has de sobreescriure el constructor de la classe Thread per modificar el nom com tindries que fer amb la la classe HelloThread.
La composició sempre és més flexible que l’herència.
I amb una expressió lambda encara és mes senzill:
val t
}Una manera senzilla de crear un thread
Però, per què hem d’implementar una interfície o ampliar una classe per crear un thread?
Pots crear un thread amb la funció thread(...) del paquet kotlin.concurrent.
En aquest cas, el codi que s’ha d’executar es passa com un argument amb el nom block:
val t
})Aquesta funció té uns quants paràmetres que et permeten configurar el thread:
-
start– sitrue, el thread es crea i s’inicia immediatament. -
isDaemon– sitrue, el thread es crea com un thread dimoni. -
contextClassLoader– un carregador de classes esepcífic per aquest thread. -
name– el nom delthread. -
priority– la prioritat del thread. -
block– el codi que ha d’executar el thread.
La funció thread() és del paquet kotlin.concurrent, recorda que l’has d’importar.
La creació d’un thread no impplica que aquest s’executi, sinó que s’ha d’iniciar la seva execució de manera explícita.
Threads d’inici
La classe Thread té un mètode amb el nom start() que s’utilitza per iniciar un thread.
El thread no s’inicia de manera inmediata, sinó que després d’invocar aquest mètode en algún moment s’invocarà el mètode runde manera automàtica.
Suposem que dins de la funció main, crees un thread anomenat t utilitzant la funció thread() i després l’inicies:
fun val t
})
t.
}Si vols, pots configurar el valor del paràmetre start com a true, o no configurar-lo (true és el valor predeterminat).
En aquest cas el thread s’iniciarà sense tenir que invocar el mètode start():
fun val t
})
}En ambdós casos el resultat serà:
Com funciona un thread
Aquí tens un gràfic que explica com comença realment un thread i per què no s’executa de manera immediata.
TODO: gràfic
Com pots veure, hi ha un cert retard entre l’inici d’un thread i el moment en què realment comença a funcionar (executar-se).
De manera predeterminada, un thread nou s’executa en el mode no dimoni .
Recordatori: La diferència entre el mode dimoni i el mode no dimoni és que la JVM no finalitzarà un programa en execució mentre encara quedin threads que no són dimonis, mentre que els threads del dimoni no impediran que la JVM finalitzi. Per tant, els threads de dimonis solen fer alguna feina en segon pla.
No confonguis els mètodes run i start!:
-
Has d’invocar
startsi vols executar el codi que està dins el mètoderunen un altre thread. -
Si invoques el mètode
rundirectament, el codi s’executarà en el mateix thread. -
Si intentes iniciar un thread més d’una vegada invocant més d’un cop el mètode
start, el mètodestarttornarà unaIllegalThreadStateExceptionel segon i els demés cops.
Tot i que dins d’un únic thread totes les instruccions s’executen seqüencialment, és impossible determinar l’ordre relatiu de les sentències entre diversos threads sense mesures addicionals que no tindrem en compte en aquesta activitat.
Considera el codi següent:
fun val t1 val t2 t1.
t2.
}L’ordre en que s’executaran els threads pot ser diferents cada cop que executes el programa.
Per exemple, el resultat podria ser aquest:
Fins i tot és possible que tots els threads puguin imprimir el seu text després que el fil principal imprimeixi “Finished”:
Això vol dir que tot i que cridem el mètode start seqüencialment per a cada thread, no sabem quan es cridarà realment el mètode run.
Quan escrius un programa mai pots saber en quin ordre s’executaran els threads si no utilitzes mecanismes de concurrència que encara no hem explicat.
Un programa senzill multithreaded
A continuació tens un programa multithreaded amb dos threads:
-
Un thread llegeix els números de l’entrada estàndard i imprimeix els seus quadrats.
-
El thread principal de tant en tant imprimeix missatges a la sortida estàndard.
Els dos threads funcionen simultàniament.
A continuació tens un thread que llegeix els números en bucle i imprimeix el seu quadrat. Té una instrucció break per aturar el bucle si el nombre donat és 0.
: override fun while val number if break
}
}
}
}Dins de la funció main, el programa inicia un objecte de la classe SquareWorkerThread, que escriu missatges a la sortida estàndard del thread principal.
fun val workerThread workerThread. // start a worker (not run!)
for if
}
}
}Aquí teniu un exemple d’entrades i sortides amb comentaris:
Com pots veure, aquest programa realitza dues tasques “al mateix temps “: una al thread principal i l’altra al thread de treball.
Pot ser que no sigui “al mateix temps” en el sentit físic si no s’executen en nuclis diferents de la CPU; no obstant això, a ambdues tasques se’ls proporciona temps de CPU per executar-se.
Activitat
1.- Tens una instància de la classe Thread anomenada thread.
Selecciona la declaració correcta:
- No podem invocar
thread.run()diverses vegades. - Si cridem al mètode
thread.run(), aquest crida al mètodestart()d’aquesta instància - No podem invocar
thread.start()més d’una vegada. - No podem crear cap altra instància de la classe
Thread
{% sol %}
No podem invocar thread.start() més d’una vegada.
Cridar el mètode run() és simplement com cridar a qualsevol altre mètode normal: s’executa dins el thread actual.
{% endsol %}
Gestió
16200
El mètode start et permet iniciar un thread en l’objecte corresponent, però a vegades és necessari gestionar el cicle de vida d’un thread mentre està funcionant en lloc d’iniciar-lo i oblidar-se.
A continuació veurem dos mètodes d’ús habitual en la programació multithread: sleep() i join().
Tots dos mètodes poden llançar una InterruptedException que no gestionarem per simplificar el codi.
Sleep
El mètode Thread.sleep() fa que el thread que s’executa actualment suspengui l’execució durant el nombre especificat de mil·lisegons.
Aquest és un mitjà eficient per fer que el temps del processador estigui disponible per als altres fils d’una aplicació o altres aplicacions que es puguin executar en un ordinador.
Sovint fem servir aquest mètode per simular invocacions que requereixen molt temps de computació o tasques difícils.
Thread. // suspend current thread for 2000 milliseconds
Vegem què fa aquest codi:
- Al començament imprimeix “Started”
- A continuació el thread actual se suspèn durant 2000 mil·lisegons (pot ser més temps, però no menys del que s’indica).
- Finalment, el thread es desperta i imprimeix “Finished”.
Una altra manera de fer dormir el thread actual és utilitzar la classe especial TimeUnit del paquet java.util.concurrent:
TimeUnit.MILLISECONDS.sleep(2000)executaThread.sleepdurant 2000 mil·lisegons.TimeUnit.SECONDS.sleep(3)executaThread.sleepdurant 3 segons (que és el mateix que 3000 mil.lisegons).
La classes TimeUnit té més períodes per escollir: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, i DAYS.
Per exemple:
import java.util.concurrent.TimeUnit
println("Started")
TimeUnit.SECONDS.sleep(3) // suspend current thread for 3 seconds
println("Finished")Join
El mètode join obliga el thread actual a esperar la finalització d’un altre thread en el qual es va cridar aquest mètode.
A l’exemple següent, l’string “The end” no s’imprimirà fins que el thred no acabi.
fun val thread: Thread
thread. // start thread
thread. // waiting for thread to die
}La versió sobrecarregada del mètode join pren el temps d’espera en mil·lisegons:
thread.Això s’utilitza per evitar esperar massa temps o fins i tot infinitament en cas que el thread es penji.
Considerem un altre exemple.
La classe Worker simula que està resolent “una tasca difícil” que necessita molt de temps:
: override fun
// it solves a difficult task
}
}Aquí tens una funció main on el thred main espera que finalitzi worker:
fun val worker worker. // start the worker
Thread.
worker. // waiting for the worker
}El thread principal espera worker i no pot imprimir el missatge “The program stopped” fins que worker finalitzi o superi el temps d’espera.
L’únic del que podem estar segurs és que:
- “Starting a task” s’imprimirà abans que “The task is finished”
- “Do something useful” s’imprimrà abans que “The program stopped”.
Hi ha diverses sortides possibles:
1.- La tasca es completa abans que es superi el temps d’espera:
2.-
3.- La tasca es completa després que es supera el temps s’espera:
4.-
Activitats
1.- Imagina que tens un objecte t que és una instància d’una classe que estén Thread.
Quin és el resultat de la invocació de t.join()?
- Els threads es fusionaran en un de sol.
tespera que finalitzi el thread actual- El thread actual espera que finalitzi
t. - Atura el tread actual fins que continuees crida
{% sol %} TODO revisar
És impossible cridar a join en un objecte d’un altre fil, només el podeu cridar al fil actual {% endsol %}
Excepcions
16422
Com ja saps, els programes poden llançar excepcions si hi ha errors en el codi i si aquesta exepció no es gestiona el programa s’atura.
El codi que s’executa dins d’un thread també pot llançar exepcions.
A continuació veurem com es comporten diversos threads quan tenen excepcions que no es gestiones dins el mateix bloc de codi..
Threads i excepcions
Si un dels threads del teu programa llança una excepció que cap mètode no detecta dins de la pila d’invocació, el thread s’acabarà.
Si aquesta excepció es produeix en un programa d’un sol thread, tot el programa s’aturarà perquè la JVM finalitza el programa en execució tan bon punt no quedin més thread que no siguin dimonis .
Aquí tens un petit exemple:
fun
}Si executes aquest programa es produeix una execpció:
El codi 1 significa que el procés ha finalitzat a causa d’un error.
En canvi, si es produeix un error dins d’un thread que no és el principal, el procés no s’atura:
fun val thread
thread. // wait for the thread with an exception to terminate
// this line will be printed
}Encara que l’exepció no es gestioni, el programa finalitzara sense cap error.
El codi 0 significa que el procés ha finalitzat correctament.
Què passarà amb els altres threads si hi ha un error al thread principal?
fun
})
}La sortida del programa serà la següent:
Pots veure que:
- El procés ha finalitzar amb un error (codi de sortida 1).
- El codi després de
print(2/ 0)no s’ha executat - El bloc de codi del thread no principal s’ha executat.
Per tant, encara que hi hagi una excepció en el thread principal el programa no s’atura, i els altres threads es segueixen executant amb normalitat.
Activitats
1.- Suposem que es produeix una excepció que no es gestiona en el thread principal .
Què passarà amb els altres threads i amb tot el procés?
- Només el thread principal s’aturarà i el codi de sortida del procés el determinarà l’últim thred finalitzat
- Només s’aturarà el thread principal, però al final el procés acabarà amb el codi 1 (Error)
- Tots els threads en execució s’aturaran immediatament i el procés finalitzarà amb el codi 1 (error). 4 . Només s’aturarà el thread principal i el procés acabarà amb el codi 0 (OK)