JEP 444, Virtual Threads
Contexte
Les threads virtuels ont été introduits dans le JDK 19 en mode aperçu, améliorés dans le JDK 20. Ils sont maintenant intégrés en standard. Cela correspond à une proposition incluant les travaux du projet Loom
Le thread au niveau de la JVM sont basés sur les threads systèmes. C’est à dire que c’est le système qui s’occupe de planifier leur execution. Cela implique aussi que le nombre de thread est limité car cela nécessite des ressources systèmes.
Objectif
L’objectif est d’avoir un thread léger qui permettent d’avoir des milliers et des millions de thread afin de réaliser des traitements concurrents.
Les threads classiques s’appellent maintenant les threads plateformes ("a plateform thread") afin de les différencier des threads virtuels.
Ce qui est intéressant, c’est que les threads virtuels héritent de la même classe java.lang.Thread
.
En revanche, il n’y a pas de constructeurs. En effet, la mode est de passer de plus en plus par des fabriques de méthodes. Pour cela, nous avons la méthode statique : Thread.startVirtualThread()
Le thread est démarré avec une instance de l’interface java.lang.Runnable
.
Ainsi, nous pouvons écrire la classe suivante :
class HelloRunnable implements Runnable {
public void run() {
System.out.println(Thread.currentThread() + " dit Coucou");
}
}
Utilisation
Nous pouvons tester les threads virtuels avec la commande jshell
.
bin/jshell
Nous allons commcer par déclarer la classe HelloRunnable
cité ci-dessus.
Ensuite, nous allons pouvoir créer notre instance.
var inst = new HelloRunnable();
Il nous reste à démarrer le thread virtuel.
Thread.startVirtualThread(inst);
A noter, que nous pouvons utiliser la méthode Thread.ofVirtual()
qui utilise le pattern Builder
Thread.ofVirtual().start(inst);
Cette méthode offre deux avantages :
-
Nous avons une méthode similaire pour les threads plateformes
Thread.ofPlatform().start(inst);
-
Nous pouvons créer une instance sans démarrer le thread (ou en tout cas sans le démarrer tout de suite.)
Thread courant = Thread.ofVirtual.unstarted(inst);
courant.start();
Pour rappel, Java 8 est passé par là avec les lambas. Donc nous pouvons écrire tout simplement :
Thread.ofVirtual().start(() -> {
System.out.println(Thread.currentThread() + " dit Coucou");
});
Remarques
En exécutant le code ci-dessus, nous obtenons la sortie suivante
VirtualThread[#25]/runnable@ForkJoinPool-1-worker-1 dit Coucou
$1 ==> VirtualThread[#25]/runnable
Le #25 est une sorte de compteur de thread virtuel. Si nous exécutons de nouveau la même commande, nous obtiendrons la sortie suivante
VirtualThread[#26]/runnable@ForkJoinPool-1-worker-1 dit Coucou
$1 ==> VirtualThread[#26]/runnable
C’est normal car nous lançons un nouveau thread virtuel. Ce qui veut dire que la première fois, il y a eu 24 thread virtuels exécutés avant notre premier appel.
Qui les utilisent ? La JVM elle-même.
Compléments sur l’API
-
Il est possible de savoir si nous sommes dans un thread virtuel ou non. Nous avons la méthode
Thread.isVirtual()
qui renvoie un booléen. -
Les méthodes
Thread.join()
etThread.sleep()
possède une surcharge prenant en paramètre une instance deDuration
.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(java.time.Duration.ofSeconds(1));
System.out.println(" " + i + " : " + Thread.currentThread());
return i;
});
});
}
A noter que dans l’exemple précédent, nous venons de créer 10 000 threads virtuels.
-
La méthode
Thread.getId()
est dépréciée. Il faut passer par la méthodeThread.threadId()
. Ce dernier renvoie un identifiant.
La raison est que la méthode getId()
n’est pas une méthode finale donc il est possible de la surcharger.
-
La méthode
Thread.getAllStackTraces()
retourne une map de toutes les threads plateforme et non tous les threads comme son nom le laisse penser.
Cela s’explique car nous pouvons avoir des millions de threads virtuels donc il n’y a pas de sens de retourner une map aussi grosse.
Différence sur l’API
-
Les threads virtuels sont forcément des threads daemon. La méthode
Thread.setDaemon()
n’a pas d’effet.
Pensez à cela pour les conditions d’arrêt de la JVM. En effet, il faut au moins un thread plateforme sinon la JVM s’arrête.
-
Dans le même style, la priorité est fixée à Thread.NORM_PRIORITY. La méthode
Thread.setPriority()
n’a pas d’effet. -
Les méthodes
stop(), suspend() et resume()
lèvent une exception lors de leur invocation si c’est un thread virtuel.
Pour rappel, ces méthodes sont déjà dépréciés au moins depuis le JDK 6.
-
Dans le même sens de l’histoire, il n’y a pas de permissions si le
SecurityManager
est activé.
Pour rappel, ce dernier est déprécié pour suppression depuis le JDK 17. Plus d’informations, dans ce billet JDK 17 Nouveautés
Nouveautés
Compatible avec les variables Thread-local
Les threads virtuels supportent maintenant les variables thread-local (ThreadLocal
).
Il faudra néanmoins faire attention. Le support est présent uniquement pour faciliter la migration et le support des librairies vers les threads virtuels. C’est à dire que cela reste à éviter au maximum vis à vis des contraintes.
Ce qu’il faut absolument éviter, c’est de faire une réserve ("un pool") de thread.
Le mieux est d’utiliser les "Scoped valued" JEP 429, mais cela est une autre JEP.
Une propriété système jdk.traceVirtualThreadLocals
permet de tracer toute utilisation de ThreadLocal
dans les threads virtuels. Par défaut, la valeur est false
.
Concurrence
Là encore, pour faciliter la transition, notamment avec l’API des Executeurs, il existe maintenant Executors.newVirtualThreadPerTaskExecutor()
qui permet d’avoir un thread virtuel par tâche.
Références
Moteur de recherche
"Eduquer, ce n'est pas remplir des vases mais c'est d'allumer des feux." - Michel Montaigne
Billets récents
- Eclipse plante systématiquement sous Debian (et autres distribution Linux)
- JEP 463, Implicitly Declared Classes and Instance Main Methods (Second Preview)
- Debian - Montée de version de Debian 11 (Bullseye) à Debian 12 (Bookworm)
- JEP 451, Prepare to Disallow the Dynamic Loading of Agents
- JEP 444, Virtual Threads