2

When a virtual thread is blocked due to synchronized call, it's yielding, so the scheduler will unmount it from related Carrier thread.

I was wondering, how does a virtual thread know that it's currently at wait state and it's time to yield?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Amit Erez
  • 21
  • 2
  • Eh? If a thread is blocked on a synchro call, it cannot know or do anything because it has no CPU execution... – Martin James Nov 26 '22 at 11:41
  • The trick with loom is: internally, there are no blocking calls. They are turned into async calls, the callback will schedule the virtual thread again, and the thread yields. – Johannes Kuhn Nov 26 '22 at 11:45

2 Answers2

3

It appears that you have a slight misconception: When a virtual thread is blocked due to synchronization, it is in fact yielding, but the scheduler will not unmount it from the related carrier thread. Instead, the carrier thread will be blocked, yielding. This is one of the current shortcomings of Project Loom, and it is well documented. The team says they might fix it in the future.

Specifically, in Coming to Java 19: Virtual threads and platform threads by Nicolai Parlog 2022-05 we read:

Unfortunately, there’s one more imperfection in the initial virtual thread proposal: When a virtual thread executes a native method or a foreign function or it executes code inside a synchronized block or method, the virtual thread will be pinned to its carrier thread. A pinned thread will not unmount in situations where it otherwise would.

(Emphasis mine.)

Further down in the same article, we read:

[...] a good alternative to synchronization is a ReentrantLock.

Note that I have read the same things in a number of other official sources of information, not just from the above blog post.

This means that at least for now, if you want to avoid blocking the carrier thread, you must refrain from using the synchronized keyword; instead, you must use a ReentrantLock or some other runtime library class that blocks waiting for something to happen while yielding, such as CountdownLatch etc.

So, to answer your question of "how does a Java virtual thread know when the thread is waiting?" the answer is that all the classes that provide block waiting while yielding (even the Sleep() method of Thread) have been modified in Java 19 so that they now co-operate with the virtual threads mechanism precisely so as to allow unmounting from the carrier thread.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • If `synchronized` is a problem, then what reason do you have to believe that `ReentrantLock` would not have exactly the same problem? – Solomon Slow Nov 26 '22 at 22:54
  • 1
    @SolomonSlow again, this has been documented. It is mentioned in the article I linked to, where it says further down "a good alternative to synchronization is a ReentrantLock". I have read it in a few more places. – Mike Nakis Nov 27 '22 at 09:31
  • 1
    Re, "this has been documented." Some of us haven't been keeping up-to-date with the latest JRE developments. And, to be fair, "ReentrantLock" does not appear in the Nicolai Parlog article except for a brief mention at the very end. After a bit of digging, I _think_ I have found the answer to my own question: It looks like virtual threads were implemented entirely in the JRE, with no additional support needed from the language or from the JVM. `ReentrantLock` is part of the JRE, but `synchronized` is part of the language, and so its behavior has not changed. – Solomon Slow Nov 27 '22 at 13:24
  • @SolomonSlow that is an interesting finding. virtual threads certainly do not have, nor do they require, any support from the language; I assumed that they required changes in the JVM, but you may be right, it may be that they implemented them exclusively on the JRE, and that may be why the `synchronized` keyword knows nothing about them. The `synchronized` keyword is internally implemented using object monitors, which are a JVM feature, not a JRE feature. If you could point me at sources that might confirm this, that would be great. – Mike Nakis Nov 27 '22 at 13:44
  • 3
    @SolomonSlow you may look into [this document](https://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html) for details, especially the section [Pinning](https://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html#pinning) which explains that `synchronized` has issued which are planned to be solved whereas the [All Your Blocking Are Belong to Us](https://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html#all-your-blocking-are-belong-to-us) section explains that all other locking mechanisms are already adapted, which was easy because they all use the same methods (`[un]park`). – Holger Dec 01 '22 at 15:27
1

At appropriate places in the code, the blocking is detected. For example

  public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            try {
                if (t.isVirtual()) {
                    VirtualThreads.park(nanos);<====
                } else {
                    U.park(false, nanos);
                }
            } finally {
                setBlocker(t, null);
            }
        }
    }
pveentjer
  • 10,545
  • 3
  • 23
  • 40