Chapter 1 Introduction
I Fundamentals
Chapter 2 Thread Safety
Chapter 3 Sharing Objects
Chapter 4 Composing Objects
Chapter 5 Building blocks
Learning java, javascript, HTML, CSS, shell, python with fun javascript games and videos
Asynchronous programming helps developers build responsive, scalable applications. In Java, two major abstractions—Future
and CompletableFuture
—are commonly used. In Python, the asyncio
framework uses coroutines
and an event loop
to handle async logic efficiently.
A Future<T>
is a handle to a result that may not yet be computed. It is returned when submitting a task to an ExecutorService
and provides methods like get()
to block until the result is ready.
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> "hello");
String result = future.get(); // blocks until task completes
While useful, Future
is limited: it is blocking and doesn’t support fluent composition of tasks.
CompletableFuture
was introduced in Java 8 to extend Future
with non-blocking, composable operations. It allows chaining logic with methods like thenApply
, thenAccept
, and thenCombine
.
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(s -> s + " world")
.thenAccept(System.out::println);
CompletableFuture
often uses a thread pool (such as ForkJoinPool.commonPool()
) and supports both I/O-bound and CPU-bound workloads.
A coroutine is a special kind of function defined with async def
that can pause and resume its execution using await
. Coroutines are scheduled by an event loop—a central scheduler that handles when and how coroutines run.
import asyncio
async def fetch_data():
await asyncio.sleep(1)
return "data"
async def main():
result = await fetch_data()
print(result)
asyncio.run(main())
Unlike threads, coroutines use cooperative multitasking. They voluntarily yield control at await
points, allowing the event loop to run other coroutines. This makes them highly efficient for I/O-bound tasks.
When running multiple asynchronous tasks concurrently and combining their results, both Python's asyncio
and Java's CompletableFuture
offer elegant solutions. Here's how they compare in behavior and syntax.
Below is a visual timeline comparing the execution flow of two tasks and a chained result:
Python asyncio
(Top Blue Bars): Uses an event loop to switch between coroutines (fetch_a
, then fetch_b
) — cooperative and sequential in appearance, even though they're both non-blocking.
Java CompletableFuture
(Bottom Green & Orange Bars): Uses threads to run tasks (futureA
and futureB
) truly in parallel, thanks to thread pool execution.
Top (Blue + Purple) – asyncio.gather()
runs fetch_a()
and fetch_b()
concurrently, then awaits a final step to combine results.
Bottom (Green + Orange + Red) – CompletableFuture.allOf()
runs futureA
and futureB
in parallel, then uses thenCombine()
to process the combined result asynchronously.
import asyncio
async def fetch_a():
await asyncio.sleep(1)
return "A"
async def fetch_b():
await asyncio.sleep(1)
return "B"
async def main():
result_a, result_b = await asyncio.gather(fetch_a(), fetch_b())
combined = f"{result_a} + {result_b}"
print("Result:", combined)
asyncio.run(main())
import java.util.concurrent.*;
public class CombinedAsync {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "A";
}, executor);
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "B";
}, executor);
futureA.thenCombine(futureB, (a, b) -> a + " + " + b)
.thenAccept(System.out::println)
.join();
executor.shutdown();
}
private static void sleep(long millis) {
try { Thread.sleep(millis); } catch (InterruptedException ignored) {}
}
}
asyncio.gather
, Java via thenCombine
.
Both approaches are powerful. Python asyncio
is good for IO-heavy single-threaded tasks, and Java CompletableFuture
gives you true parallelism or more flexible composition with error handling.
Concept | Java Future | Java CompletableFuture | Python asyncio + coroutines |
---|---|---|---|
Core abstraction | Future | Future + reactive chaining | Coroutine (async def ) |
Execution model | Thread pool (manual) | Thread pool (default async) | Single-threaded event loop |
At the time of this blog post, in the area of Asynchronous programming, Java is the winner. As virtual threads become permanent feature since JDK 21, it is trending to move away from async and reative programming.
Testing concurrent programs is a critical aspect of software development. It ensures that multithreaded applications behave correctly and perform efficiently under various workloads. Chapter 12 of Java Concurrency in Practice outlines the essential strategies for testing concurrent systems—from correctness to performance and scalability—and highlights the risks and tools involved in doing so.
Correctness testing focuses on ensuring that a concurrent program does not suffer from data races, deadlocks, or unexpected behavior due to thread interactions. These are broadly divided into:
A good example is the SemaphoreBoundedBuffer, which uses semaphores to manage concurrent access to a bounded buffer. The accompanying test classes demonstrate how to validate both blocking and non-blocking behaviors. These tests include checking that:
Beyond correctness, performance testing addresses how the program behaves under load. We usually test for:
In TestThreadPool, we simulate load against a thread pool and observe how it performs under concurrent task submission. This helps evaluate how well the thread pool scales, responds under pressure, and maintains throughput over time.
Performance testing concurrent code is difficult due to the inherent non-determinism of thread scheduling and execution. Common pitfalls include:
To mitigate these issues:
In 2025, developers have access to more advanced tools and practices that make testing concurrent programs more practical and reliable:
AI test generation concurrency
shift-left concurrency testing
LitmusKt concurrency testing
JMH concurrency benchmarks
pytest asyncio concurrency testing
Testing concurrent programs is a combination of art and engineering. It starts with unit tests that verify correctness, followed by performance tests that measure how systems behave under stress. With modern tools and thoughtful test design, we can build systems that are not only safe and correct but also scalable and responsive under real-world conditions.
In the next chapter, we’ll explore explicit locking and conditions—and how to use them safely and effectively when high-level abstractions aren’t enough.
TestThreadPool.java
This test uses a TestingThreadFactory that maintains a count of created threads, so that test cases can verify the number of threads created during a test run.