Search code examples
javamultithreadingvirtual-threads

Difference of behaviors between Virtual Threads & Classical Threads regarding 'synchronized'?


A lot of open-source projects are trying to get away from using syncrhonized due to its behavior with Virtual Thread.

Regarding Virtual Threads, I understand that within a synchronized block, in situations where network communication (which would normally park the thread) or file I/O occurs, the Carrier Thread should ideally be handed over to another thread, but it remains "pinned." Is my understanding correct?

Is the situation different with traditional ThreadPool-based multithreading? In cases where asynchronous processing occurs within a synchronized block, can the Carrier Thread be properly handed over to another thread?

Additionally, does a similar issue occur during the process of acquiring a Lock to execute a synchronized block? In other words, when another thread is executing a synchronized block and the Virtual Thread is waiting, does it remain pinned until the lock is released?

My questions could be summarized as below:

public void someMethod() {
    // Does Virtual Thread get pinned here, too?
    synchronized(this) {
        someIOMethods() // Virtual Thread gets 'pinned' here, right? How about classical ThreadPools?
    }
}

Solution

  • public void someMethod() {
        // Does Virtual Thread get pinned here, too?
        synchronized(this) {
            someIOMethods() // Virtual Thread gets 'pinned' here, right?...
        }
    }
    

    A virtual thread that calls someMethod() will be pinned to its carrier while it executes inside the synchronized block. It doesn't "get pinned" again when it calls someIOMethods() because it already is pinned. The someIOMethods() call happens inside the synchronized block, and the thread is pinned for the entire duration of that block.

    ...How > about classical ThreadPools?

    "Pinned" means, the carrier thread is unable to execute the code of any other virtual thread until it exits from the synchronized block.

    A classic thread pool worker thread can only work on one task at a time. Once it picks a task from the pool's task queue, it is unable to execute the code of any other task until it completes the one that it picked. Compared to how virtual threads work, it's as if the thread pool worker is always "pinned." It was pinned before it ever entered someMethod().