JEP 444 - Virtual Threads
Threads virtuels légers pour simplifier et améliorer la programmation concurrente en Java
En résumé
Les threads virtuels sont des threads légers gérés par la JVM plutôt que par le système d'exploitation. Ils permettent d'écrire du code concurrent simple (style thread-per-request) tout en supportant des millions de threads simultanés. Révolution majeure : combinez la simplicité du code synchrone avec les performances de l'asynchrone, sans réécrire votre code.
Contexte et motivation
Le problème avec les threads traditionnels
Les threads Java traditionnels (appelés platform threads) sont coûteux car :
- Chaque thread correspond à un thread OS (système d'exploitation)
- Création et changement de contexte sont lents
- Consommation mémoire importante (~1 Mo par thread)
- Limite au nombre de threads (quelques milliers max)
Exemple typique : une application web qui traite des requêtes. Avec le modèle "un thread par requête", vous êtes limité à quelques milliers de requêtes simultanées.
Solutions existantes et leurs limites
Pour contourner ce problème, plusieurs approches ont été développées :
- Programmation asynchrone (CompletableFuture, réactive) : performant mais complexe, code difficile à lire et débuguer
- Thread pools : réutilise les threads mais ne résout pas le problème fondamental de leur coût
Solution : les threads virtuels
Les threads virtuels apportent le meilleur des deux mondes :
- ✅ Simplicité : code synchrone classique, facile à lire et débuguer
- ✅ Performance : des millions de threads sans problème
- ✅ Compatibilité : API java.lang.Thread existante, migration facile
- ✅ Pas de réécriture : votre code existant fonctionne avec les virtual threads
Comment ça marche ?
Les threads virtuels sont :
- Gérés par la JVM (pas par l'OS)
- Très légers (~1 Ko en mémoire)
- Montés sur des platform threads (carrier threads) au besoin
- Automatiquement "parkés" lors d'opérations bloquantes (I/O, sleep, etc.)
Quand un virtual thread fait une opération bloquante, la JVM le met en pause et libère le platform thread pour qu'un autre virtual thread puisse l'utiliser.
Exemples de code
Créer un virtual thread
// Avant (platform thread)
Thread thread = new Thread(() -> {
System.out.println("Hello from platform thread");
});
thread.start();
// Avec virtual threads
Thread vThread = Thread.startVirtualThread(() -> {
System.out.println("Hello from virtual thread");
});
Utiliser un ExecutorService avec virtual threads
// Créer un executor qui utilise des virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Soumettre 10 000 tâches (impossible avec platform threads !)
for (int i = 0; i < 10_000; i++) {
int taskId = i;
executor.submit(() -> {
// Simulation d'un appel réseau
Thread.sleep(Duration.ofSeconds(1));
return "Résultat de la tâche " + taskId;
});
}
} // Auto-fermeture et attente de toutes les tâches
Serveur web simple avec thread-per-request
void handleRequest(Socket socket) throws IOException {
// Code synchrone simple et lisible
var request = readRequest(socket);
var user = fetchUser(request.userId()); // Appel DB
var orders = fetchOrders(user.id()); // Appel DB
var response = buildResponse(user, orders);
writeResponse(socket, response);
}
// Serveur principal
try (var serverSocket = new ServerSocket(8080)) {
while (true) {
Socket socket = serverSocket.accept();
// Un virtual thread par requête
Thread.startVirtualThread(() -> handleRequest(socket));
}
}
💡 Remarque : Ce code peut gérer des dizaines/centaines de milliers de connexions simultanées, tout en restant simple à comprendre. Avec des platform threads, cela serait impossible.
Utilisation avec Thread.ofVirtual()
// Builder pour plus de contrôle
Thread vThread = Thread.ofVirtual()
.name("mon-virtual-thread")
.start(() -> {
System.out.println("Exécution du virtual thread : " + Thread.currentThread());
});
// Attendre la fin
vThread.join();
Exemple complet : traitement parallèle
import java.time.Duration;
import java.util.concurrent.*;
import java.util.stream.IntStream;
public class VirtualThreadsDemo {
public static void main(String[] args) throws InterruptedException {
// Mesurer le temps avec platform threads
long startPlatform = System.currentTimeMillis();
testWithPlatformThreads();
long timePlatform = System.currentTimeMillis() - startPlatform;
System.out.println("Platform threads: " + timePlatform + " ms");
// Mesurer le temps avec virtual threads
long startVirtual = System.currentTimeMillis();
testWithVirtualThreads();
long timeVirtual = System.currentTimeMillis() - startVirtual;
System.out.println("Virtual threads: " + timeVirtual + " ms");
}
static void testWithPlatformThreads() throws InterruptedException {
try (var executor = Executors.newFixedThreadPool(100)) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100));
return i;
});
});
}
}
static void testWithVirtualThreads() throws InterruptedException {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100));
return i;
});
});
}
}
}
Bonnes pratiques
✅ À faire
- Utiliser des virtual threads pour les tâches I/O-bound (réseau, DB, fichiers)
- Créer des millions de virtual threads sans hésitation
- Écrire du code synchrone simple et lisible
- Utiliser
Executors.newVirtualThreadPerTaskExecutor()
❌ À éviter
- Ne pas utiliser de thread pools avec virtual threads (pas nécessaire !)
- Éviter les opérations CPU-intensives dans des virtual threads (préférer platform threads)
- Ne pas utiliser
synchronizedsur des sections longues (préférer ReentrantLock) - Éviter ThreadLocal si possible (coût mémoire multiplié par le nombre de virtual threads)
Impact et bénéfices
Pour le développeur
- Code plus simple et plus lisible
- Debugging et stack traces normales
- Pas besoin d'apprendre la programmation réactive
- Migration progressive depuis le code existant
Pour l'application
- Meilleure utilisation des ressources
- Latence réduite
- Scalabilité améliorée (plus de connexions simultanées)
- Coût infrastructure potentiellement réduit
Compatibilité et migration
Les virtual threads sont compatibles avec l'API Thread existante :
- Tous les outils de monitoring et profiling fonctionnent
- Les debuggers supportent les virtual threads
- Les librairies existantes fonctionnent (JDBC, HttpClient, etc.)
- Migration progressive possible (mélanger platform et virtual threads)
🎯 Migration recommandée : Commencez par remplacer vos ExecutorService par
newVirtualThreadPerTaskExecutor()dans les parties I/O de votre application.