Butcher virtual threads like a pro!
Piotr Przybył
17 IV 2024
© 2024 Piotr Przybył. Licensed under CC BY-NC-SA 4.0
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.
Picture by Pete Linforth from Pixabay
$ whoami
Piotr Przybył
piotrprz
@piotrprz@mstdn.social
Senior Developer Advocate

Java Champion
Testcontainers Community Champion
Oracle ACE Associate
Trainer
CAVEAT AVDITORES!
A.K.A. Safe harbour statement: don't assume anything based on this presentation. Verify on
your own. Errare humanum est.
Preview features
--enable-preview
More on preview features
Concurrency
platform / OS threads can be too slow to create
in limited number, hence no thread-per-request
reactive is not easy to debug and troubleshoot
lack of async / await
Virtual Threads / Loom
Biggest change since Lambda?
Scoped Values
🧪 preview feature in 2️⃣2️⃣
JEP-464
Corner stone: Virtual Threads
not reinvent the wheel, keep threads concept
cheap to start
carried by platform / OS threads under the hood
unmounting carrier threads when waiting, e.g. for IO
always daemons, with normal prio
There is no magic switch to turn the Threads you currently have into Virtual Threads
You need to alter your source code
- Thread.ofVirtual(Runnable r).start()
- Thread.startVirtualThread(Runnable r)
- Executors.newVirtualThreadPerTaskExecutor()
- Thread.ofVirtual().factory()
Some frameworks allow easy switch to virtual threads
Virtual Threads will not magically squeeze more juice from your CPU!
But your application will scale better ;-)
What not to do with Virtual Threads
-Djdk.tracePinnedThreads=full
and JFR events
Structured concurrency
- better "idioms" for multi-threaded code
- helps to eliminate thread leaks and cancellation delays
- not replacing interruption with cancellation (for now)
Structured concurrency
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var namesSubtask = scope.fork(()-> nameService.getNames());
var scoresSubtask = scope.fork(()-> scoreService.getScores());
scope.join();
scope.throwIfFailed();
var names = namesSubtask.resultNow();
var scores = scoresSubtask.resultNow();
return combine(names, scores);
}
free cancellation within the tree, yay!
Structured concurrency
StructuredTaskScope.ShutdownOnFailure()
StructuredTaskScope.ShutdownOnSuccess<T>()
don't forget join/joinUntil
and that the subtasks should be interruptible!
Scoped Values
one-way immutable "ThreadLocals"
+
bounded lifetime (visible in code)
=
simplified reasoning
and improved performance
(ThreadLocal doesn't change and doesn't get deprecated)
Scoped Values
static final ScopedValue<Level> SECURITY_CLEARANCE_LEVEL =
ScopedValue.newInstance();
// ...
ScopedValue.where(SECURITY_CLEARANCE_LEVEL, levelFromRequest())
.run(() -> {
var level = SECURITY_CLEARANCE_LEVEL.orElse(guestLevel());
if (level.permits()) {
doSomeStuff();
doSomeOtherStuff();
} else {
error();
}
ScopedValue.where(SECURITY_CLEARANCE_LEVEL, NO_ACCESS_LEVEL)
.run(() -> log(progress);
});
Scoped Values
- work nicely with Structured Concurrency
- run(Runnable r) or call(Callable c)
- where(...) can be nested with its own scope
as long as you don't mutate the darn thing!
So how can we butcher
virtual threads & Co.?
- use VTs mainly for CPU heavy tasks
- swarm tasks without any flow control
- ignore unmounting for IO
- pool, reuse and mix VTs with synchronized
- ignore interruptions
- mutate scoped values