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 Thread
s 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