A virtual thread executes on carrier threads, and the monitor-related carrier-pinning behavior changed in JDK 24 without removing the monitor’s mutual-exclusion contract.
Java 25 virtual threads and monitors: version boundaries still matter
Java 25+. The program uses JDK classes and requires no preview flags.
Separate contention from carrier retention
A synchronized operation still allows only one holder of a particular monitor at a time. Removing the older monitor-related pinning behavior does not make all requests inside that critical section execute concurrently. A slow operation under one shared monitor can remain an application bottleneck.
The Java 21 lesson is labelled for that release. Its synchronized-block pinning warning should not be generalized to every newer JDK. This Java 25 fixture checks completed increments under a monitor and uses virtual threads, but it does not infer carrier behavior from elapsed time.
Native or foreign calls can introduce other blocking or pinning boundaries. Runtime diagnostics and the actual call path matter. A virtual thread also does not create more database connections, CPU cores or queue capacity; downstream admission remains a separate application rule.
Wait for completion before reading shared state
The program joins every worker before printing the count. The protected increment prevents lost updates, while joining supplies a completion boundary for the final read. A collection of started Thread objects alone does not prove that their operations finished successfully in a larger application.
Working program
import java.util.*;
public class MonitoredVirtualReceipts {
final Object monitor=new Object();int accepted;
void accept(){synchronized(monitor){accepted++;}}
public static void main(String[] args)throws Exception{
MonitoredVirtualReceipts service=new MonitoredVirtualReceipts();List<Thread> workers=new ArrayList<>();
for(int i=0;i<40;i++)workers.add(Thread.ofVirtual().start(service::accept));
for(Thread worker:workers)worker.join();
System.out.println(service.accepted);
}
}Output
40Costs and boundaries
There are n operations and n retained worker handles. The monitor serializes their increments, so virtual threads do not establish parallel speedup for that section. This program verifies the state result on Java 25; timing, native-call behavior and carrier scheduling need separate diagnostic evidence.
Common Mistakes
- Less monitor pinning does not remove lock contention.
- A release-specific warning is not a timeless rule.
- Uncaught worker failure requires explicit reporting in application code.
