Virtual Threads
As an Android developer, now I use Kotlin all the time.
In Java 19, Java brought the feature of virtual threading, which is all too familiar to Kotlin, a Java version of Coroutines.
As you know, OS switching threads is an expensive affair. Java’s Thread is a wrapper implementation for OS Thread, so there are as many Java threads as there are OS threads.
Like this
But most of the time we my operations are lightweight, but using an entire thread is very wasteful.
One of the most important concepts of virtual threads is
Non-blocking hangs
- Co-threading(Virtual Thread) is cutting threads
- A hang is a tangent thread that can automatically cut back
- Hanging is non-blocking in the sense that it can write non-blocking operations in code that looks blocking
So Virtual Thread are not the real Thread, it a Carrier Thread. It’s still run in the Thread, and if you have only one business it has the same overhead as regular Thread.
Java’s Virtual Threads are mainly these two functions
Thread.ofVirtual()
Executors.newVirtualThreadPerTaskExecutor()
Here’s an example
private static void runOnThread() {
final AtomicInteger atomicInteger = new AtomicInteger();
Runnable runnable = () -> {
try {
Thread.sleep(Duration.ofMillis(500));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Work Done - " + atomicInteger.incrementAndGet());
};
Instant start = Instant.now();
try (var executor = Executors.newFixedThreadPool(100)) {
for(int i = 0; i < 500; i++) {
executor.submit(runnable);
}
}
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
System.out.println("Thread total time: " + timeElapsed + "ms");
}
private static void runOnVirtualThread() throws InterruptedException {
final AtomicInteger atomicInteger = new AtomicInteger();
Runnable runnable = () -> {
try {
Thread.sleep(Duration.ofMillis(500));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Work Done - " + atomicInteger.incrementAndGet());
};
Instant start = Instant.now();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for(int i = 0; i < 500; i++) {
executor.submit(runnable);
}
}
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
System.out.println("Virtual Thread total time: " + timeElapsed + "ms");
}
We perform the same task using Threads and Virtual Threads respectively.
Use Thread.sleep(Duration.ofMillis(500));
to simulate time-consuming IO blocking tasks.
Thread total time: 2530ms
Virtual Thread total time: 510ms
The task in this example is simple code-sleep for half second-modern hardware can easily support 500 virtual threads running this kind of code simultaneously.
JDK runs the code on a small number of OS threads, perhaps just one.
But in runOnThread()
, JDK really created 500 threads.
That’s a huge expense. 💵