Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save DanielThomas/0b099c5f208d7deed8a83bf5fc03179e to your computer and use it in GitHub Desktop.
Save DanielThomas/0b099c5f208d7deed8a83bf5fc03179e to your computer and use it in GitHub Desktop.
import java.time.Duration;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Demonstrate potential for deadlock on a {@link ReentrantLock} when there is both a synchronized and
* non-synchronized path to that lock, which can allow a virtual thread to hold the lock, but
* other pinned waiters to consume all the available workers.
*/
public class VirtualThreadReentrantLockDeadlock {
public static void main(String[] args) {
final boolean shouldPin = args.length == 0 || Boolean.parseBoolean(args[0]);
final ReentrantLock lock = new ReentrantLock(true); // With faireness to ensure that the unpinned thread is next in line
lock.lock();
Runnable takeLock = () -> {
try {
System.out.println(Thread.currentThread() + " waiting for lock");
lock.lock();
System.out.println(Thread.currentThread() + " took lock");
} finally {
lock.unlock();
System.out.println(Thread.currentThread() + " released lock");
}
};
Thread unpinnedThread = Thread.ofVirtual().name("unpinned").start(takeLock);
List<Thread> pinnedThreads = IntStream.range(0, Runtime.getRuntime().availableProcessors())
.mapToObj(i -> Thread.ofVirtual().name("pinning-" + i).start(() -> {
if (shouldPin) {
synchronized (new Object()) {
takeLock.run();
}
} else {
takeLock.run();
}
})).toList();
lock.unlock();
Stream.concat(Stream.of(unpinnedThread), pinnedThreads.stream()).forEach(thread -> {
try {
if (!thread.join(Duration.ofSeconds(3))) {
throw new RuntimeException("Deadlock detected");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment