diff --git a/README.md b/README.md index 8c02d83..2b71275 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,44 @@ multiple times won't cause errors. - [Javadoc](https://javadoc.io/doc/com.autonomouslogic.commons/commons-java/latest/com/autonomouslogic/commons/Stopwatch.html) - [Source](https://github.com/autonomouslogic/commons-java/blob/main/src/main/java/com/autonomouslogic/commons/Stopwatch.java) +## VirtualThreads + +Executes I/O-bound tasks on virtual threads with bounded concurrency. Virtual threads are lightweight +and can handle blocking I/O efficiently without tying up platform threads for APIs, database calls, and +file operations. + +**Key capabilities:** + +- **`callAll()`** - Execute callables/functions and collect results in submission order + ```java + List urls = List.of("https://api1.com", "https://api2.com"); + List responses = VirtualThreads.callAll(urls, url -> fetchUrl(url), 2); + // results in order: responses.get(0) is from urls.get(0) + ``` + +- **`runAll()`** - Execute runnables with bounded concurrency (no results) + ```java + List files = List.of("file1.txt", "file2.txt", "file3.txt"); + VirtualThreads.runAll(files, filename -> processFile(filename), 5); + ``` + +- **`isVirtual()`** / **`checkIsVirtual()`** - Detect if running on a virtual thread + ```java + if (VirtualThreads.isVirtual()) { + // Safe to block on I/O without hurting throughput + } + ``` + +- **`onVirtualThread()`** - Execute a task on a virtual thread if not already on one + ```java + String result = VirtualThreads.onVirtualThread(() -> blockingDatabaseCall()); + ``` + +- [Javadoc](https://javadoc.io/doc/com.autonomouslogic.commons/commons-java/latest/com/autonomouslogic/commons/concurrent/VirtualThreads.html) +- [Source](https://github.com/autonomouslogic/commons-java/blob/main/src/main/java/com/autonomouslogic/commons/concurrent/VirtualThreads.java) +- [Oracle Virtual Threads Documentation](https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html) +- [JEP 444: Virtual Threads](https://openjdk.org/jeps/444) + ## Other Common Libraries * [Apache Commons](https://commons.apache.org/) diff --git a/src/main/java/com/autonomouslogic/commons/concurrent/VirtualThreads.java b/src/main/java/com/autonomouslogic/commons/concurrent/VirtualThreads.java index 6f2fa16..a5f5b11 100644 --- a/src/main/java/com/autonomouslogic/commons/concurrent/VirtualThreads.java +++ b/src/main/java/com/autonomouslogic/commons/concurrent/VirtualThreads.java @@ -7,6 +7,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -15,6 +16,65 @@ import java.util.stream.Stream; import lombok.NonNull; +/** + * Utilities for executing tasks on virtual threads with bounded concurrency. + * + *

What are virtual threads? + * Virtual threads are lightweight threads managed by the JVM. + * Unlike traditional platform threads (which map 1:1 to OS threads), many virtual threads can run on a single + * platform thread, making them extremely cheap to create and suspend. They're ideal for I/O-bound workloads. + * + *

When to use this class: + *

    + *
  • I/O-bound tasks: Calling APIs, reading files, database queries. Virtual threads excel here because + * they efficiently handle blocking operations without tying up platform threads.
  • + *
  • Bounded concurrency needed: You want parallelism but need to limit it (e.g., API rate limits, + * database connection pools). Use {@code maxConcurrency} to control resource usage.
  • + *
  • Result ordering matters: Methods return results in submission order, making it easy to correlate + * inputs with outputs.
  • + *
  • Fail-fast semantics preferred: If one task fails, the remaining tasks are canceled immediately.
  • + *
+ * + *

Usage examples: + * + *

Execute callables and get results in order: + *

{@code
+ * List urls = List.of("http://api1.com", "http://api2.com", "http://api3.com");
+ * List responses = VirtualThreads.callAll(
+ *     urls,
+ *     url -> fetchContent(url),
+ *     2  // max 2 concurrent requests
+ * );
+ * // responses.get(0) corresponds to urls.get(0), etc.
+ * }
+ * + *

Execute runnables with bounded concurrency: + *

{@code
+ * List files = List.of("file1.txt", "file2.txt", "file3.txt");
+ * VirtualThreads.runAll(
+ *     files,
+ *     filename -> processFile(filename),
+ *     5  // max 5 concurrent file operations
+ * );
+ * }
+ * + *

Check if running on a virtual thread: + *

{@code
+ * if (VirtualThreads.isVirtual()) {
+ *     // Safe to block on I/O without harming throughput
+ *     var data = readDataFromDatabase();
+ * }
+ * }
+ * + *

Error handling: The first task failure causes all remaining tasks to be canceled (via + * {@link ExecutorService#shutdownNow()}). The exception is propagated to the caller. The executor is cleaned up + * properly in all cases (success, failure, interruption). + * + * @see Thread#ofVirtual() + * @see Executors#newVirtualThreadPerTaskExecutor() + * @see Oracle Virtual Threads Documentation + * @see JEP 444: Virtual Threads + */ public class VirtualThreads { /** * Executes tasks on virtual threads with bounded concurrency.