At a time when Herbt Sutter announced to everyone that the free lunch is over (The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software), concurrency has become our everyday life.A big change is coming to Java, the Loom project and with it such new terms as "virtual thread", "continuations" and "structured concurrency". If you've been wondering what they will change in our daily work or
whether it's worth rewriting your Tomcat-based application to super-efficient reactive Netty,or whether to wait for Project Loom? This presentation is for you.
I will talk about the Loom project and the new possibilities related to virtual wattles and "structured concurrency". I will tell you how it works and what can be achieved and the impact on performance
14. Concurrency: throughput(tasks/time unit)
Schedule multiple largely independent tasks to a set of computational resources
14
Parallelism: latency(time unit)
Speed up a task by splitting it to sub-tasks and exploiting multiple processing units
21. Thread
• Unit of work
• Too less them
• Requires a lot of resources
• Hard to manage (Reactive toys 😎)
• Not enough knowledge
• Lazy programmers
21
25. Platform Thread
• ~1ms to schedule thread
• Big memory consumption: 2MB of stack
• Expensive
• OS thread
• Task-switching requires switch to kernel: ~100µs (depends on the OS)
• Scheduling is a compromise for all usages. Bad cache locality
25
29. Virtual Thread
• Lighter threads
• Less memory usage
• Fastest blocking code*
• No more platform threads
• is not GC root
• CPU cache misses are possible
29
• Pay-as-you-go stacks (size 200-300
bytes) stored in a heap
• Scales to 1M+ on commodity
hardware
• Clean stack traces
• Your old code just works
• Readable sequential code
• The natural unit of scheduling for
operating systems
30. Virtual Thread
• Cheap to create
• Cheap to destroy
• Cheap to block
30
https://cojestgrane24.wyborcza.pl/cjg24/Warszawa/1,30,33618,Smerfy-live-on-stage-na-Torwarze.html
31. Purpose?
• mostly intended to write I/O application
• servers
• message brokers
• higher concurrency if system needs additional resources for concurrency
• available connections in a connection pool
• su
ffi
cient memory to serve the increased load
• increase e
ffi
ciency for short cycle tasks
31
32. Virtual Thread isn’t for
• The non-realtime kernels primarily employ time-sharing when the CPU is
at 100%
• Run for long time
• CPU bound tasks*
32
33. „Virtual threads are not an execution resource, but a business logic
object like a string.”
33
34. 34
final Thread thread1 = Thread
.ofPlatform()
.unstarted(() -> System.out.println("Hello from " + Thread.currentThread()));
final Thread thread2 = Thread
.ofVirtual()
.unstarted(() -> System.out.println("Hello from " + Thread.currentThread()));
Hello from Thread[#22,Thread-0,5,main]
Hello from VirtualThread[#23]/runnable@ForkJoinPool-1-worker-1
35. Fast forward to today
• Virtual thread = user mode thread
• Scheduled by JVM, not OS
• Virtual thread is a instance of java.lang.Thread
• Platform thread is instance of java.lang.Thread but implemented by
“traditional way”, thin wrapper around OS thread
35
37. How are virtual threads implemented?
• Built on continuations, as lower construct of JVM
• Virtual thread wraps a task in continuation
• FIFO mode
• M:N threading model
37
43. A scheduler assigns continuations to CPU cores, replacing a paused one
with another that's ready to run, and ensuring that a continuation that is
ready to resume will eventually be assigned to a CPU core.
43
47. Copy Terminology
• Freeze: Suspend a continuation and unmount it by copying frames from
OS thread stack → continuation object
• Thaw: Mount a suspended continuation by copying frames from
continuation object → OS thread stack
47
50. 50
private static void enter(Continuation c, boolean isContinue) {
// This method runs in the "entry frame".
// A yield jumps to this method's caller as if returning from this method.
try {
c.enter0();
} finally {
c.finish();
}
}
private void enter0() {
target.run();
}
67. I/O
• The java.nio.channels classes — SocketChannel, ServerSocketChannel and DatagramChannel — were retro
fi
tted to
become virtual-thread-friendly. When their synchronous operations, such as read and write, are performed on a virtual thread,
only non-blocking I/O is used under the covers.
• “Old” I/O networking — java.net.Socket, ServerSocket and DatagramSocket — has been reimplemented in Java on top
of NIO, so it immediately bene
fi
ts from NIO’s virtual-thread-friendliness.
• DNS lookups by the getHostName, getCanonicalHostName, getByName methods of java.net.InetAddress (and other
classes that use them) are still delegated to the operating system, which only provides a OS-thread-blocking API. Alternatives are
being explored.
• Process pipes will similarly be made virtual-thread-friendly, except maybe on Windows, where this requires a greater e
ff
ort.
• Console I/O has also been retro
fi
tted.
• Http(s)URLConnection and the implementation of TLS/SSL were changed to rely on j.u.c locks and avoid pinning.
• File I/O is problematic. Internally, the JDK uses bu
ff
ered I/O for
fi
les, which always reports available bytes even when a read will
block. On Linux, we plan to use io_uring for asynchronous
fi
le I/O, and in the meantime we’re using the
ForkJoinPool.ManagedBlocker mechanism to smooth over blocking
fi
le I/O operations by adding more OS threads to the
worker pool when a worker is blocked.
67
80. Project Synergies
• Data more local than ever
• Less reason to manually share data across thread pools
• Same data no are private in per request model
• GC when thread terminates
• The Virtual thread stack objet itself is thread-local
80
98. ConcurrentHashMap#computeIfAbsent
„Some attempted update operations on this map by other threads
may be blocked while computation is in progress, so the
computation should be short and simple, and must not attempt to
update any other mappings of this map.”
98
99. 99
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
public class CHMPinning {
public static void main(String... args) throws InterruptedException {
Map<Integer, Integer> map = new ConcurrentHashMap<>();
for (int i = 0; i < 1_000; i++) {
int finalI = i;
Thread.startVirtualThread(() -> map.computeIfAbsent(finalI % 3, key -> {
try {
Thread.sleep(2_000);
} catch (InterruptedException e) {
throw new CancellationException("interrupted");
}
return finalI;
}));
}
long time = System.nanoTime();
try {
Thread.startVirtualThread(() -> System.out.println("Hi, I'm an innocent virtual thread")).join();
} finally {
time = System.nanoTime() - time;
System.out.printf("time = %dms%n", (time / 1_000_000));
}
System.out.println("map = " + map);
}
}
100. 100
private static final ConcurrentMap<String, String> cache = new ConcurrentHashMap<>();
private static String refresh(String key) {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
scope.fork(() -> UUID.randomUUID().toString());
scope.join();
return scope.result();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
var cpus = Runtime.getRuntime().availableProcessors();
List<Future> fl = new ArrayList<>();
try (var es = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < cpus; ++i)
fl.add(es.submit(() -> cache.computeIfAbsent("foo", k -> refresh(k))));
}
for (var f : fl)
System.out.println(f.get());
}
108. Future work
• BlockingQueue
• Structured Concurrency
• use io_uring for asynchronous
fi
le I/O
• Object.wait()
• Concurrent Collection review
108
109. Takeaways
• Nothing is changed 😃
• A virtual thread is a java.lang.Thread — in code, at
runtime, in the debugger and in the pro
fi
ler
• Lighter threads
• Pay-as-you-go stacks (size 200-300 bytes) stored in a heap
• Scales to 1M+ on commodity hardware
• Clean stack traces
• Your old code just works
• Readable sequential code
• The natural unit of scheduling for operating systems
109
• Your old code just works
• Readable sequential code
• The natural unit of scheduling for operating systems
• Clean stack traces
• A virtual thread is not a wrapper around an OS thread, but a
Java entity.
• Creating a virtual thread is cheap — have millions, and don’t
pool them!
• Blocking a virtual thread is cheap — be synchronous!
• No language changes are needed.
• Pluggable schedulers o
ff
er the
fl
exibility of asynchronous
programming.
110. Takeaways
• Move to simpler blocking/synchronous code
• Migrate tasks to Virtual threads not Platform threads to Virtual threads
• Use Semaphores or similar to limit concurrency
• Try to not cache expensive objects in Thread Locals
• Avoid pinning
• Avoid reusing
• Avoid pooling
110