-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Background
According to https://openjdk.org/jeps/425:
There are two scenarios in which a virtual thread cannot be unmounted during blocking operations because it is pinned to its carrier:
- When it executes code inside a synchronized block or method, or
- When it executes a native method or a foreign function.
Pinning does not make an application incorrect, but it might hinder its scalability. If a virtual thread performs a blocking operation such as I/O or BlockingQueue.take() while it is pinned, then its carrier and the underlying OS thread are blocked for the duration of the operation. Frequent pinning for long durations can harm the scalability of an application by capturing carriers.
The scheduler does not compensate for pinning by expanding its parallelism. Instead, avoid frequent and long-lived pinning by revising synchronized blocks or methods that run frequently and guard potentially long I/O operations to use java.util.concurrent.locks.ReentrantLock instead. There is no need to replace synchronized blocks and methods that are used infrequently (e.g., only performed at startup) or that guard in-memory operations. As always, strive to keep locking policies simple and clear.
Other references (solutions & problems)
- Loom compatible - replace synchronized block with for example j.u.c.ReentrantLock pgjdbc/pgjdbc#1951 & refactor:(loom) replace the usages of synchronized with ReentrantLock pgjdbc/pgjdbc#2635
- Make HikariCP loom friendly brettwooldridge/HikariCP#1463
- Compatibility with virtual threads (OpenJDK's Project Loom) spring-projects/spring-framework#23443
- Replace synchronized blocks with reentrant locks FirebirdSQL/jaybird#702
- Using ReentrantLock(s) on allocator can cause deadlocks using Virtual Threads netty/netty#13204
Discussion
I personally opt-in for the ReentrantLock usage pattern introduced by the pgjdbc team:
public final class ResourceLock
extends ReentrantLock
implements AutoCloseable
{
@MustBeClosed
public ResourceLock obtain()
{
lock();
return this;
}
@Override
public void close()
{
this.unlock();
}
}
This is then used in try-with-resource blocks like this:
try (ResourceLock ignored = lock.obtain()) {
// critical section
}
This plays nicely with existing tools/checks that we have (https://errorprone.info/bugpattern/MustBeClosedChecker)