Lesson Completion
Back to course

Virtual Threads and Modern Features: Lightweight Concurrency

Beginner
12 minutes4.8Java

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

FeaturePlatform ThreadsVirtual Threads
Cost~2MB stack~Few KB
CreationExpensiveCheap
BlockingWastes OS threadUnmounts from carrier
Max count~ThousandsMillions
SchedulingOS schedulerJVM 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 ThreadsVirtual ThreadsUse When
Limited (1K-10K)MillionsHigh concurrency
Expensive blockingCheap blockingI/O-bound tasks
OS-scheduledJVM-scheduledMany 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 thread

Pro-Tips:

  1. 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(() -> ...)
  1. 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)

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01