The other answers have adequately addressed your issue, however I would like to share a novel approach towards the goal of "cleanly acquiring a collection of mutexes". Due to the nature of the synchronized
block in Java, it's not feasible to acquire several mutexes in turn (the loop would essentially need to be unrolled).
Object[] mutexes = new Object[4];
for (int i=0; i < 4; i++) mutexes[i] = new Object();
synchronized (mutexes[0]) {
synchronized (mutexes[1]) {
synchronized (mutexes[2]) {
synchronized (mutexes[3]) {
// all acquired
}
}
}
}
However, if you look at the resultant bytecode, you'll see that each synchronized
block is opened with a MONITORENTER
instruction and explicitly closed with a MONITOREXIT
instruction. If we had direct access to these operations, we could iterate once to enter each monitor and then iterate again to exit each monitor. Is it possible to compile valid Java code that does this? Sort of.
JNI exposes these methods in the form of JNIEnv::MonitorEnter and JNIEnv::MonitorExit. With this in mind, we can do the following:
public final class MultiLock {
public static void run(Object[] mutexes, Runnable task) {
monitorEnter(mutexes);
try {
task.run();
} finally {
monitorExit(mutexes);
}
}
private static native void monitorEnter(Object[] arr);
private static native void monitorExit(Object[] arr);
}
#include "MultiLock.h" // Header generated by javac
#include <stdlib.h>
#include <stdbool.h>
static inline bool is_valid_monitor(JNIEnv *env, jobject object) {
return object != NULL;
}
JNIEXPORT void JNICALL Java_MultiLock_monitorEnter(JNIEnv *env, jclass ignored, jobjectArray arr) {
jsize len = (*env)->GetArrayLength(env, arr);
jobject next;
for (jsize i = 0; i < len; i++) {
next = (*env)->GetObjectArrayElement(env, arr, i);
if (!is_valid_monitor(env, next)) continue;
(*env)->MonitorEnter(env, next);
}
}
JNIEXPORT void JNICALL Java_MultiLock_monitorExit(JNIEnv *env, jclass ignored, jobjectArray arr) {
jsize len = (*env)->GetArrayLength(env, arr);
jobject next;
if (len == 0) return;
for (jsize i = len - 1; i >= 0; i--) {
next = (*env)->GetObjectArrayElement(env, arr, i);
if (!is_valid_monitor(env, next)) continue;
(*env)->MonitorExit(env, next);
}
}
And use it like so:
// Load the natives somehow
Object[] mutexes = new Object[4];
for (int i=0; i < 4; i++) mutexes[i] = new Object();
MultiLock.run(mutexes, () -> {
// all acquired
});