Fils a Java
Estats d’un fil
L’API de baix nivell de Java es basa en l’objecte Thread, que pot tenir una sèrie d’estats.
- NEW: Un fil que no s’ha iniciat.
- RUNNABLE: Un fil que s’està executant a la JVM. És possible que no tingui la CPU.
- BLOCKED: Un fil que està esperant per entrar a un bloc sincronitzat (esperant un monitor lock).
- WAITING: Un fil que espera indefinidament per un altre (operacions wait, join).
- TIMED_WAITING: Un fil que espera un cert temps per un altre (operacions sleep, wait, join).
- TERMINATED: Un fil que ha acabat. No es pot tornar a executar.
Crear i executar un fil
Podem crear un fil de dues maneres:
- Estendre la classe Thread i reescriure el mètode “run” (millor no utilitzar aquest mètode).
- Implementar la interfície Runnable i el seu mètode “run”. Llavors, crear un Thread passant aquest objecte al constructor:
new Thread(new MyRunnable())
Un cop tenim el Thread, el podem executar mitjançant el seu mètode start(), que farà canviar el seu estat de NEW a RUNNABLE.
Per als exemples que veurem a continuació, utilitzarem la següent utility class:
public class Threads {
private static long start = System.currentTimeMillis();
private Threads() {}
public static void log(String message) {
System.out.println(
String.format(
"%6d %-10s %s",
System.currentTimeMillis() - start,
Thread.currentThread().getName(),
message));
}
public static void spend(long millis) {
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < millis)
;
}
public static void rest(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Els seus mètodes poden cridar-se després de fer un import static Threads.*;.
A continuació, podem veure un exemple de creació d’un fil anomenat “fil” des del fil principal, “main”. Mostra l’estat del fil “child” abans i després de la seva execució.
Aquest seria el codi:
public class StatesThread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("spending");
spend(750);
log("resting");
rest(750);
log("ending");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable(), "child");
log(thread.getState().name());
thread.start();
rest(250);
log(thread.getState().name());
rest(875);
log(thread.getState().name());
rest(500);
log(thread.getState().name());
}
}
Aquesta podria ser una sortida per pantalla possible:
0 main NEW
8 child spending
258 main RUNNABLE
758 child resting
1133 main TIMED_WAITING
1508 child ending
1633 main TERMINATED
Aquest exemple no presenta dificultats, ja que no es comparteix cap dada. Tampoc s’utilitza cap mecanisme de sincronització entre els dos fils.
Fils de plataforma i fils virtuals
Tots els fils que hem creat fins ara són fils de plataforma (platform threads): cadascun és un fil del sistema operatiu, amb la seva pila reservada i planificat pel sistema. Són cars, i per això una aplicació en té centenars i no milions.
Des de Java 21 hi ha una segona mena de fil, el fil virtual (virtual thread), que gestiona la JVM. Crear-ne un costa molt poc, i quan es bloqueja esperant una operació d’entrada/sortida, la JVM el retira del fil de plataforma que l’estava executant i hi posa una altra feina. El fil del sistema operatiu no es queda mai esperant.
Es creen amb el constructor ofVirtual() o amb el mètode de conveniència startVirtualThread():
Thread virtual = Thread.ofVirtual().name("worker").start(new MyRunnable());
Thread altre = Thread.startVirtualThread(new MyRunnable());
virtual.join();
altre.join();
També hi ha Thread.ofPlatform(), que crea un fil de plataforma amb la mateixa sintaxi.
Els dos tipus són objectes Thread i s’utilitzen igual: start(), join(), interrupt() i els estats funcionen exactament com hem vist. Hi ha dues diferències que convé saber:
- Un fil virtual és sempre daemon: no manté viva l’aplicació. Si
mainacaba, els fils virtuals pendents moren, de manera que cal esperar-los ambjoin()o amb un executor. - La prioritat no s’hi aplica.
A la pràctica no crearàs fils virtuals d’un en un, sinó que els deixaràs gestionar per un executor, com es veu a Llibreria Java concurrent.