Skip to content

Instantly share code, notes, and snippets.

@HomoEfficio
Last active February 16, 2026 12:39
Show Gist options
  • Select an option

  • Save HomoEfficio/31c75c0774851c294c87e324f4be466c to your computer and use it in GitHub Desktop.

Select an option

Save HomoEfficio/31c75c0774851c294c87e324f4be466c to your computer and use it in GitHub Desktop.
In Java 21, a virtual thread is mounted from a carrier thread while waiting to acquire a lock of the synchronized block.
import java.util.List;
import java.util.stream.IntStream;
public class ThreadPinningSynchronizedTest {
private static final Object lock = new Object();
public static void main(String[] args) {
// Give some time for JFR ready
giveSomeTimeForJFRInit();
List<Thread> threads = IntStream.range(0, 10)
.mapToObj(index -> Thread.ofVirtual().unstarted(() -> {
System.out.println(index + " BEFORE " + Thread.currentThread());
// HERE!
// A virtual thread is unmounted from its carrier while waiting for the lock.
synchronized (lock) {
// No blocking but takes long time
getFibonacci(40);
}
System.out.println(index + " AFTER " + Thread.currentThread());
}))
.toList();
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// n-th Fibonacci number
private static long getFibonacci(int n) {
if (n <= 1) {
return n;
}
return getFibonacci(n - 1) + getFibonacci(n - 2);
}
private static void giveSomeTimeForJFRInit() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
/*
The result using JDK 21
9 BEFORE VirtualThread[#37]/runnable@ForkJoinPool-1-worker-10
4 BEFORE VirtualThread[#32]/runnable@ForkJoinPool-1-worker-5
0 BEFORE VirtualThread[#28]/runnable@ForkJoinPool-1-worker-1
1 BEFORE VirtualThread[#29]/runnable@ForkJoinPool-1-worker-2
2 BEFORE VirtualThread[#30]/runnable@ForkJoinPool-1-worker-3
5 BEFORE VirtualThread[#33]/runnable@ForkJoinPool-1-worker-6
3 BEFORE VirtualThread[#31]/runnable@ForkJoinPool-1-worker-4
7 BEFORE VirtualThread[#35]/runnable@ForkJoinPool-1-worker-8
8 BEFORE VirtualThread[#36]/runnable@ForkJoinPool-1-worker-9
6 BEFORE VirtualThread[#34]/runnable@ForkJoinPool-1-worker-7
9 AFTER VirtualThread[#37]/runnable@ForkJoinPool-1-worker-10
6 AFTER VirtualThread[#34]/runnable@ForkJoinPool-1-worker-8
8 AFTER VirtualThread[#36]/runnable@ForkJoinPool-1-worker-2
7 AFTER VirtualThread[#35]/runnable@ForkJoinPool-1-worker-7
3 AFTER VirtualThread[#31]/runnable@ForkJoinPool-1-worker-1
5 AFTER VirtualThread[#33]/runnable@ForkJoinPool-1-worker-9
2 AFTER VirtualThread[#30]/runnable@ForkJoinPool-1-worker-3
1 AFTER VirtualThread[#29]/runnable@ForkJoinPool-1-worker-5
0 AFTER VirtualThread[#28]/runnable@ForkJoinPool-1-worker-4
4 AFTER VirtualThread[#32]/runnable@ForkJoinPool-1-worker-6
가장 먼저 락을 획득한 i == 9 일 때를 제외하면 BEFORE/AFTER 의 worker thread 번호가 동일하다.
즉, 가상 스레드가 캐리어 스레드를 점유하지 않고 자원을 낭비하지 않는다.
All virtual threads waiting for the lock are unmounted from the initially mounted carrier threads.
So the virtual threads does not occupy the carrier thread and does not waste the resource while waiting for the lock.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment