Last active
February 16, 2026 12:39
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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