1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Virtual threads (Java 21+, Project Loom) = lightweight threads (millions possible!). 1:M mapping (many virtual → few platform threads). Simple concurrency (same Thread API).
- Create: Thread.ofVirtual().start(Runnable) or Executors.newVirtualThreadPerTaskExecutor().
- Benefits: Massive scalability (10K+ connections), simple programming model (no callbacks!), cheap creation/blocking.
- Sequenced collections (Java 21): Ordered collections with first(), last(), reversed().
- Use for: Web servers, I/O-bound apps, high concurrency!
Think of hotel rooms vs tents. Platform threads = hotel rooms (expensive, limited, heavyweight). Virtual threads = tents (cheap, millions possible, lightweight). Same guest experience (Thread API), different infrastructure!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Platform thread: Heavyweight process (OS thread, expensive)
- Virtual thread: Lightweight task (JVM-managed, cheap)
- Carrier thread: Worker that executes virtual threads
Virtual Thread Architecture
graph TD
A[10,000 Virtual Threads] --> B[Carrier Thread 1]
A --> C[Carrier Thread 2]
A --> D[Carrier Thread N]
B --> E[OS Thread 1]
C --> F[OS Thread 2]
D --> G[OS Thread N]
style A fill:#2E7D32
style B fill:#F57C00
style E fill:#C2185B
3. Technical Mastery (The "Deep Dive")
Virtual Threads vs Platform Threads
| Feature | Platform Threads | Virtual Threads |
|---|---|---|
| Cost | ~2MB stack | ~Few KB |
| Creation | Expensive | Cheap |
| Blocking | Wastes OS thread | Unmounts from carrier |
| Max count | ~Thousands | Millions |
| Scheduling | OS scheduler | JVM scheduler |
4. Interactive & Applied Code
java
import java.time.Duration;
import java.util.concurrent.*;
import java.util.*;
import java.util.stream.*;
public class VirtualThreadsDemo {
public static void main(String[] args) throws Exception {
demonstrateCreation();
demonstrateScalability();
demonstrateWithExecutor();
demonstrateSequencedCollections();
}
// Creating virtual threads
static void demonstrateCreation() throws Exception {
System.out.println("=== CREATING VIRTUAL THREADS ===");
// Method 1: Thread.ofVirtual()
Thread vt1 = Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread 1: " + Thread.currentThread());
});
vt1.join();
// Method 2: Thread.startVirtualThread()
Thread vt2 = Thread.startVirtualThread(() -> {
System.out.println("Virtual thread 2: " + Thread.currentThread());
});
vt2.join();
// Platform thread for comparison
Thread pt = Thread.ofPlatform().start(() -> {
System.out.println("Platform thread: " + Thread.currentThread());
});
pt.join();
}
// Scalability demonstration
static void demonstrateScalability() throws Exception {
System.out.println("\n=== SCALABILITY ===");
int taskCount = 10_000;
// ❌ Platform threads: Would exhaust resources!
// ExecutorService platform = Executors.newCachedThreadPool();
// ✅ Virtual threads: Handles millions easily
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<?>> futures = new ArrayList<>();
long start = System.currentTimeMillis();
for (int i = 0; i < taskCount; i++) {
Future<?> future = executor.submit(() -> {
try {
Thread.sleep(Duration.ofMillis(100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
futures.add(future);
}
// Wait for all
for (Future<?> future : futures) {
future.get();
}
long elapsed = System.currentTimeMillis() - start;
System.out.println(taskCount + " tasks in " + elapsed + "ms");
}
}
// Virtual thread executor
static void demonstrateWithExecutor() throws Exception {
System.out.println("\n=== EXECUTOR SERVICE ===");
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Submit tasks
Future<String> future1 = executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100));
return "Result 1";
});
Future<String> future2 = executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100));
return "Result 2";
});
System.out.println(future1.get());
System.out.println(future2.get());
}
}
// Sequenced Collections (Java 21)
static void demonstrateSequencedCollections() {
System.out.println("\n=== SEQUENCED COLLECTIONS ===");
List<String> list = new ArrayList<>(List.of("A", "B", "C", "D"));
// New methods
System.out.println("First: " + list.getFirst()); // "A"
System.out.println("Last: " + list.getLast()); // "D"
// Remove first/last
list.removeFirst();
list.removeLast();
System.out.println("After removal: " + list); // [B, C]
// Reversed view (not a copy!)
List<String> reversed = list.reversed();
System.out.println("Reversed: " + reversed); // [C, B]
// Modify reversed view affects original
reversed.set(0, "X");
System.out.println("Original: " + list); // [B, X]
}
}
// Real-world example: Web server
class WebServerExample {
public static void main(String[] args) throws Exception {
// Simulate web server handling requests
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Handle 10K concurrent connections
for (int i = 0; i < 10_000; i++) {
int requestId = i;
executor.submit(() -> handleRequest(requestId));
}
}
System.out.println("All requests handled!");
}
static void handleRequest(int id) {
try {
// Simulate I/O (database, API call)
Thread.sleep(Duration.ofMillis(100));
// Virtual thread unmounts during sleep (doesn't waste carrier thread!)
System.out.println("Request " + id + " handled");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Structured concurrency (preview)
class StructuredConcurrencyExample {
record User(String name) {}
record Order(int id) {}
static void fetchUserData(int userId) {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<User> user = scope.fork(() -> fetchUser(userId));
Future<Order> order = scope.fork(() -> fetchOrder(userId));
scope.join(); // Wait for all
scope.throwIfFailed(); // Propagate errors
System.out.println("User: " + user.resultNow());
System.out.println("Order: " + order.resultNow());
} catch (Exception e) {
e.printStackTrace();
}
}
static User fetchUser(int id) throws Exception {
Thread.sleep(Duration.ofMillis(100));
return new User("Alice");
}
static Order fetchOrder(int id) throws Exception {
Thread.sleep(Duration.ofMillis(100));
return new Order(123);
}
}5. The Comparison & Decision Layer
| Platform Threads | Virtual Threads | Use When |
|---|---|---|
| Limited (1K-10K) | Millions | High concurrency |
| Expensive blocking | Cheap blocking | I/O-bound tasks |
| OS-scheduled | JVM-scheduled | Many short tasks |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Why can virtual threads scale to millions?" Answer: Virtual threads unmount when blocking (don't waste carrier threads)!
java
// Platform thread: Blocks carrier + OS thread
Thread.ofPlatform().start(() -> {
Thread.sleep(1000); // ❌ Wastes OS thread for 1 second!
});
// Virtual thread: Unmounts when blocking
Thread.ofVirtual().start(() -> {
Thread.sleep(1000); // ✅ Unmounts! Carrier thread free for others
});
// M:N mapping
// 1 million virtual threads → ~10 carrier threads → ~10 OS threads
// Virtual thread blocks → unmounts → carrier picks up another virtual threadPro-Tips:
- Don't pool virtual threads:
java
// ❌ BAD: Pooling defeats the purpose!
ExecutorService pool = Executors.newFixedThreadPool(10); // Platform threads
// ✅ GOOD: Create per-task
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
// Or: Thread.startVirtualThread(() -> ...)- Avoid thread-local with virtual threads:
java
// ❌ Expensive with millions of virtual threads
ThreadLocal<Connection> connLocal = new ThreadLocal<>();
// ✅ Better: Pass as parameter or use scoped values (preview)