Java 20 : un outillage prend forme autour des threads virtuels

Java 20

Java 20 fait son entrée. Des valeurs délimitées à la concurrence structurée, un outillage se constitue en support des threads virtuels.

Comment favoriser la montée en charge des applications serveur ? Sur la plate-forme Java, le mécanisme des threads virtuels s’impose progressivement. Avec la sortie de JDK 20, l’API en est désormais à sa deuxième phase de preview.

Dans le modèle « traditionnel » de programmation « une requête = un thread », Java encapsule les threads du système d’exploitation. Une technique d’autant plus coûteuse en ressources que cette stack OS est partagée entre langages et runtimes. La mutualisation des threads – afin qu’ils gèrent plusieurs requêtes – apporte une forme de solution. Mais à un certain prix : elle requiert une programmation asynchrone, divisant la logique de traitement en étapes généralement traduites par des expressions lambda.

Java 20 et les valeurs délimitées

Pour accompagner la mise en œuvre de ces threads virtuels, on a doté Java d’un nouveau type de valeur, actuellement en incubation. L’idée : proposer une alternative aux variables locales, avec une structure moins complexe, pour permettre le partage de données inter- et intra-threads.

Les variables locales ressemblent à des variables ordinaires, mais elles ont une incarnation par thread. Elles sont mutables : tout code qui peut appeler la méthode get() peut appeler la méthode set(…). Un attribut qui permet des flux de données multidirectionnels, mais susceptible de rendre les programmes moins lisibles.
Il arrive par ailleurs que les variables locales soient conservées plus longtemps que nécessaire. Elles sont en l’occurrence retenues pour tout le cycle de vie de leur thread, à moins d’appeler la méthode remove()… que les développeurs zappent parfois. L’empreinte mémoire peut, en outre, croître d’autant plus que les threads enfants héritent de ces variables.

Les valeurs expérimentées (scoped values) sont immuables une fois définies. La structure syntaxique du code détermine la période pendant laquelle le thread associé peut y accéder.

La concurrence structurée toujours en incubation

Également en incubation, il y a le mécanisme de concurrence structurée. Le principe : pouvoir grouper les tâches de différents threads en unités de travail. Autant pour améliorer la gestion des erreurs que renforcer l’observabilité.

Dans du code « classique » monothread, les sous-tâches s’exécutent séquentiellement. Mais si elles sont suffisamment indépendantes les unes des autres et qu’on dispose des ressources idoines, on peut les paralléliser. Les threads virtuels favorisent cette approche, mais leur coordination peut s’avérer un défi.

La concurrence structurée est censée aider cette démarche en préservant la lisibilité et la maintenabilité : la structure de la tâche doit refléter la structure du code (le runtime peut constituer un arbre hiérarchique, équivalent de la pile d’appel d’un thread).

On aura aussi noté l’entrée de l’API Foreign Function & Memory en deuxième phase de preview. Unifiant les API Foreign-Memory et Foreign Linjer, elle est censée offrir une alternative plus sûre à la JNI (Java Native Interface) permettre aux programmes d’exploiter du code et des zones mémoires situés hors de la JVM. En toile de fond, des bibliothèques comme Ignite, Lucene et TensorFlow, qui tolèrent difficilement l’imprédictibilité du ramasse-miettes et doivent donc stocker des données hors du tas.

Photo d’illustration © iuriimotov – Adobe Stock