JEP 425, Virtual Threads (Preview)

Contexte

Les threads virtuels ont été proposés dans le JDK 19. Ils sont maintenant intégré dans le build 22 du JDK 19 en accès anticipé. 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 que le nombre de thread est limité par le système.

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. Vu que les threads virtuels sont en mode aperçu. Il est nécessaire d’activer les aperçus avec l’option --enable-preview

bin/jshell --enable-preview

Maintenant, nous allons pouvoir créer notre classe HelloRunnable suivi d’une 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.

astuceQui 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() et Thread.sleep() possède une surcharge prenant en paramètre une instance de Duration.

  • La méthode Thread.getId() est dépréciée. Il faut passer par la méthode Thread.threadId(). Ce dernier renvoie un identifiant.

  • La méthode Thread.getAllStackTraces() retourne une map de toutes les threads plateforme et non tous les threads comme son nom le laisse penser.

astuceCela 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.

Difference sur l’API

  • Les threads virtuels sont forcément des threads daemon. La méthode Thread.setDaemon() n’a pas d’effet.

avertissementPensez à 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.

avertissementPour 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é.

avertissementPour rappel, ce dernier est déprécié pour suppression depuis le JDK 17. Plus d’informations, dans ce billet JDK 17 Nouveautés