It is a known caveat that virtual threads do not work well with long running synchronization by pinning the thread. That unfortunately means that for many applications it may be premature to adopt them, but it is mature enough for broader evaluation by the libraries and frameworks. The Java team provided a status of their efforts recently [1].
Sorry, the first sentence is a mis-informing wording.
The `synchronized` pins the thread only when from within of the `synchronized` the program calls a blocking operation that would normally unmount the virtual thread, like blockingQueue.take() or similar. (Which is not a sane coding practice). It's because the unmounting, as it's implemented today, does not work well with synchronized.
It's better if people read JEP 444 than rely on forum comments, to avoid being misinformed.
Speaking of long-running - even without synchronized, a long running code keeps the native thread occupied, until some blocking operation is called. So an endless loop that does not call a virtual-thread-ready blocking operation will occupy the native thread forever.
Java virtual threads are a kind of cooperative multithreading - another virtual thread only gets chance to kick-in when some current virtual thread reaches specific blocking operations. In contrast to preemptive multi-threading with native threads.
So I agree with your conclusion. Virtual threads can not (yet?) be blindly used as a drop-in replacement of native threads for existing code. And the new code needs to take their specifics into account.
BTW, another method I discovered to block the native carrier thread that executes a virtual thread is to call blocking reading through FileInputStream, for example reading from the console. The FileInputStream does not implement virtual thread parking at all (yet?).
The issue in this case isn't actually the synchronized block. The thread is blocked on Object.wait, which releases the monitor before sleeping. The problem is that Object.wait is implemented in native code still, which pins the thread. The idea is that these days wait isn't exactly deprecated but there are better concurrency tools available, so they upgraded those first, leaving the Java 1 style concurrency tools for later. And Java 1 style concurrency has been improved on but is hardly insane, it can work well enough in many situations and is sometimes the basis for higher level concurrency utilities.
By long running I just meant anything that was not fast compute. I was more focused on finding the reference link so I agree my wording wasn’t clear.
Go started without preemption and added it later. The Java team has indicated a similar path, so we might see that tackled in the future. I think they could do that using safe points or JEP 312‘s handshakes, so it’s not infeasible.
For file io they wanted to explore io_ring and they might need to add a loom friendly resolver for JEP 418. There is just so much left, like scalable timers, that I think it’s going to be a long time until VTs will be a good default choice.
To get the whole context, so virtual threads are unusable?
What holds a monitor by default and is there a workaround?
Found more:
A virtual thread cannot be unmounted during blocking operations when it is pinned to its carrier. A virtual thread is pinned in the following situations:
The virtual thread runs code inside a synchronized block or method
The virtual thread runs a native method or a foreign function (see Foreign Function and Memory API)
For those those that don't know what this means: Blocking network TCP IO needs a sychronized block to work = you can't use virtual threads for networking. I wish they formulated it like that from the start!
Atleast now we know what they meant with don't use virtual threads for anything but tasks <- not blocking IO with synchronization!
So for now manual NIO is still the king of the hill.
We are reaching peak humanity levels of complexity!
> For those those that don't know what this means: Blocking network TCP IO needs a sychronized block to work = you can't use virtual threads for networking
That’s not true — blocking TCP IO is not implemented as blocking under the hood - that’s the whole point of virtual threads, so your conclusion is faulty.
Perhaps it’s better to say that they are not yet general purpose. There are many caveats which need to be resolved and are being actively worked on. I would not use them broadly yet, but that could change rapidly.
A monitor will pin the VT to the carrier thread. That can have surprising incompatibility in the current jdk. Soon these footguns will be fixed and you can use them worry free.
The problem is not “long running synchronization” but synchronization that relies on stuff running outside of virtual threads to unblock itself. There is no issue beyond performance if you perform filesystem operations in your mounted state.
Yeah, I wasn’t being particular about this exact issue and was generalizing about synchronization pinning the carrier. A deadlock is trivial once the implications of that are thought through.
https://www.youtube.com/watch?v=WoQJnnMIlFY&t=421s