2

I am playing around with Project Loom for the first time and I have some code

try (var executor = Executors.newVirtualThreadExecutor()) {
    IntStream.range(0, 16).forEach(i -> {
        System.out.println("i = " + i + ", Thread ID = " + Thread.currentThread());
        executor.submit(() -> {
            System.out.println("Thread ID = " + Thread.currentThread());
        });
    });
}

with output like

Thread ID = VirtualThread[#37]/runnable@ForkJoinPool-1-worker-4
Thread ID = VirtualThread[#33]/runnable@ForkJoinPool-1-worker-5
i = 9, Thread ID = Thread[#1,main,5,main]
Thread ID = VirtualThread[#43]/runnable@ForkJoinPool-1-worker-9
Thread ID = VirtualThread[#46]/runnable@ForkJoinPool-1-worker-11
i = 10, Thread ID = Thread[#1,main,5,main]
i = 11, Thread ID = Thread[#1,main,5,main]

Is there a way I can tell what Carrier Thread each Virtual Thread is running on?

Does ForkJoinPool-1-worker-11 represent a particular Carrier (Platform) Thread, or does it mean something else?

Holger
  • 285,553
  • 42
  • 434
  • 765
Eric Kolotyluk
  • 1,958
  • 2
  • 21
  • 30

2 Answers2

3

Yes, this suffix is the name of the current carrier thread.

When I use the following code

public static void main(String[] args) throws InterruptedException {
    Set<String> threadStrings = ConcurrentHashMap.newKeySet();
    try(var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        executor.invokeAll(Collections.nCopies(16,
            () -> threadStrings.add(Thread.currentThread().toString())));
    }
    System.out.println("\tSimple Run");
    threadStrings.stream().sorted().forEachOrdered(System.out::println);

    threadStrings.clear();

    try(var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        executor.invokeAll(Collections.nCopies(16, () -> {
            threadStrings.add(Thread.currentThread().toString());
            Thread.sleep(100);
            return threadStrings.add(Thread.currentThread().toString());
        }));
    }
    System.out.println("\tWith wait");
    threadStrings.stream().sorted().forEachOrdered(System.out::println);
}

It prints

        Simple Run
VirtualThread[#15]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#17]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#18]/runnable@ForkJoinPool-1-worker-3
VirtualThread[#19]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#20]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#21]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#22]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#23]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#24]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#25]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#26]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#27]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#28]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#29]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#30]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#31]/runnable@ForkJoinPool-1-worker-4
        With wait
VirtualThread[#36]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#37]/runnable@ForkJoinPool-1-worker-3
VirtualThread[#37]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#38]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#38]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#39]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#39]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#40]/runnable@ForkJoinPool-1-worker-5
VirtualThread[#40]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#41]/runnable@ForkJoinPool-1-worker-6
VirtualThread[#41]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#42]/runnable@ForkJoinPool-1-worker-7
VirtualThread[#42]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#43]/runnable@ForkJoinPool-1-worker-5
VirtualThread[#43]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#44]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#44]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#45]/runnable@ForkJoinPool-1-worker-5
VirtualThread[#45]/runnable@ForkJoinPool-1-worker-6
VirtualThread[#46]/runnable@ForkJoinPool-1-worker-5
VirtualThread[#46]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#47]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#49]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#49]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#50]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#50]/runnable@ForkJoinPool-1-worker-6
VirtualThread[#51]/runnable@ForkJoinPool-1-worker-3
VirtualThread[#51]/runnable@ForkJoinPool-1-worker-5
VirtualThread[#52]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#52]/runnable@ForkJoinPool-1-worker-8

(results may vary)

demonstrating how the carrier thread might change when performing a sleep. But in the current snapshot (“build 18-loom+6-282”) it’s not possible to specify your own Executor anymore and there is no method for querying the virtual thread about the carrier thread it uses (other than the implicit hint via toString()). So, the management of the underlying host threads is mostly a black box in this version.

Keep in mind that this is an ongoing development. It’s not clear whether and how this will change.

Holger
  • 285,553
  • 42
  • 434
  • 765
1

Note to self for your reusable sleep method :)_

Name your virtual threads 'virtualThread-' and then check that the name starts with 'virtualThread-'; https://davidvlijmincx.com/posts/naming-virtual-threads/