Threads, executors, and virtual threads
From `Thread` to `ExecutorService` to Project Loom — concurrency the modern way.
Runs on a sandboxed JVM via the public Piston API. Edit the code and hit Run.
Threads: the low-level primitive
new Thread(runnable).start() creates a platform thread mapped 1:1 to an OS thread. Threads are expensive — a few MB of stack each — so creating them per task doesn't scale. You rarely instantiate Thread directly in modern code; you hand tasks to an executor.
ExecutorService
Executors.newFixedThreadPool(n) reuses a bounded pool of threads. submit(Callable) returns a Future you can .get() for the result. Always shut down the pool: try (var pool = Executors.newFixedThreadPool(4)) { ... } uses try-with-resources (Java 19+) to close it cleanly.
Virtual threads (Project Loom)
Java 21 finalized virtual threads — lightweight threads scheduled by the JVM on top of a small pool of carrier threads. You can spawn millions of them. They shine for I/O-bound work: blocking code (Thread.sleep, blocking I/O) becomes cheap because the JVM parks the virtual thread and reuses the carrier for other work.
Runs on a sandboxed JVM via the public Piston API. Edit the code and hit Run.
CompletableFuture
For chained async work without blocking, use CompletableFuture. supplyAsync starts a computation, thenApply transforms the result, thenCompose chains another async step, exceptionally recovers from failure. It's Java's answer to promises.
Shared state and locks
Concurrency bugs are almost always about shared mutable state. Prefer immutable data and message-passing. When you must share, use AtomicInteger/AtomicReference for single fields, ReentrantLock or synchronized for compound updates, and ConcurrentHashMap for maps. Never assume HashMap is safe under concurrent writes — it isn't, and the failure mode is data corruption, not an exception.
Structured concurrency (preview)
The StructuredTaskScope API (finalized in Java 21+) treats a group of concurrent subtasks as a single unit of work — they succeed or fail together, and the parent waits for all of them. It removes an entire class of leak bugs and pairs beautifully with virtual threads.