When using virtual threads in Java 21 there is a way to limit the number of underlying platform threads by specifying jdk.virtualThreadScheduler.parallelism and jdk.virtualThreadScheduler.maxPoolSize as described in this answer. If I set these variables to 1, the execution of all virtual threads will be multiplexed on a single OS thread as I understand. Do I still need to use synchronisation mechanisms like ReentrantLock and volatile in that case, or would it be safe to skip them altogether as long as the shared data is only accessed from the virtual threads?
2 Answers
Synchronization/locking prevents subject instructions from being interlaced. Virtual or not doesn't change that.
Remember the old days with single cpu and multiple threads guaranteed to never run instruction in parallel; we still needed synchronization in case of cpu context switching.
4 Comments
volatiles you asked about.Do I still need to use synchronisation mechanisms like ReentrantLock and volatile in that case
In your vision of virtual threads you concentrate on Continuation, the concept of platform Carrier thread picking up ready-to-go virtual thread and continue the execution of behalf of the latter. But you seem to miss another concept, crucial to virtual threads: Context Switching. When a Carrier thread gets dismounted by one virtual thread and gets mounted on by another, all the footprints (Context) of the first one are eliminated from the Carrier and the appropriate state (Context) of second one is loaded. This includes not only locks and intrinsic synchronization monitors (volatile, I think, is another story as it is more simple code-based insertion of memory barriers and does not represent Context state), but also set of ThreadLocals.
Therefore, your next statement
the shared data is only accessed from the virtual threads
is not fully correct even if you will somehow manage to limit the amount of Carrier threads to only one (which still might be marginally possible if you configure your own Executor for virtual threads). The virtual threads, even if they are always mounted on the same Carrier, don't share more data than the traditional platform ones.
2 Comments
ThreadLocals, but at the face value it's the same. As for blocking calls - not only there, a Carrier thread, when a VT mounts on it, is also a subject of OS time-slicing, VT can be interrupted by OS scheduler in the midst of CPU-bound activity, but again the same as with platform thread.
jdk.virtualThreadScheduleroptions won't help you with memory consistency effects.ForkJoinPoolimplementation ofExecutor. If you look at this one, you might realize that it is impossible to restrict the FJP to using only one thread.