4

In Java, suppose you have an array:

Object[] objs = {o1, o2, ... , oN};

And a critical section:

{
 critical();
}

And you wish to execute the critical section while holding the intrinsic lock of every element in objs.

I can think of one way to do this, and it involves an abominable abuse of recursion:

void syncArray(int i) {
  if (i >= 0) {
    synchronized(objs[i]) {
       syncArray(i - 1);
    }
  } else {
    critical();
  }
}

syncArray(objs.length - 1);

Besides being ugly, this entails O(N) stack frames, which is probably not great. Is there a better way? What I really want is a way to acquire and release intrinsic locks without the synchronized keyword. Bonus points if you have a non-blocking way to attempt to acquire an intrinsic lock.

Note: I'm not asking if this is a good idea (it's not), just if it's possible. The real-world answer is obviously just to use explicit locks and also do some soul-searching about the wisdom of trying to acquire locks for N objects at once.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
ljp
  • 135
  • 6
  • 1
    Presumably you could "unroll" by 2 or 4 and take 4 locks with 4 nested `synchronized(objs[i-0..3])` blocks, when `i>=4`. That might JIT into less-terrible asm, if the recursion doesn't just turn into a loop. – Peter Cordes Aug 14 '20 at 02:33
  • Is using `objs` as the lock not an option to you? – akuzminykh Aug 14 '20 at 03:04
  • Other threads may be trying to acquire the locks of individual objects while this is happening, so using objs as the lock wouldn't have quite the right effect. – ljp Aug 14 '20 at 03:08
  • 1
    So answering my own question a bit: I got to looking up whether I could just wrap the monitorenter and monitorexit bytecode instructions in their own functions, so they could be called without being paired. It turns out that this code might get trashed by the JVM implementation, because the spec says it is "permitted but not required" to enforce that the number of monitorenter and monitorexit calls must be the same for an object upon function exit. This means what I'm asking for in this question is impossible! https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10 – ljp Aug 14 '20 at 03:15
  • 2
    While that does sound as though you can't put those instructions in their own functions, could you construct a function which issues them inside loops? – tgdavies Aug 14 '20 at 03:22
  • 1
    So you're considering whether this is possible with Java bytecode directly, regardless of whether you can write Java source that will compile to such bytecode? (Or via the [tag:java-bytecode-asm] library to create it). – Peter Cordes Aug 14 '20 at 03:45
  • Peter: I was trying to do it with pure Java, but failing that, we can entertain bytecode / generated code solutions. Those definitely aren't as satisfying, though. – ljp Aug 14 '20 at 03:54
  • Can't you use an array of java.util.concurrent.locks.Lock instead? The thing you're trying to do looks very odd, I'm afraid. The intrinsic lock that you refer to, more precisely, Object monitors, don't exactly work the way you want them to work. Lock objects do, however. – Marcio Lucca Aug 14 '20 at 04:16

1 Answers1

2

This is not possible in ordinary Java code¹, but could be done on the bytecode level.

However, doing so would not be worth the effort, as it doesn’t solve the issue of your recursive approach, the stack consumption, which seems to be your only concern.

This has been elaborated in Does synchronized block have max reentrant limit? The example program in this answer demonstrates that bytecode acquiring an object monitor in a loop exhibits a direct relationship between available stack space and maximum number of monitor acquisitions you can make.

In other words, even the handcrafted bytecode acquiring the object monitors in a loop has an O(n) stack consumption and may fail for larger arrays the same way as your recursive approach.

The example code of the linked answer acquires the monitor of the same object in a loop but if even that doesn’t get optimized, there is no reason to assume that acquiring monitors of different objects could get away with less stack space.

Regarding your “bonus question”, there is no bytecode operation for a non-blocking “try‑monitorenter”. Some versions of sun.misc.Unsafe have a tryMonitorEnter method, but that is out of any standard.

Regarding the structured locking requirement you mentioned in a comment, this imposes no problem. It only requires that upon method exit, the method must not hold any intrinsic locks it didn’t already hold at entry. So if you method acquires all monitors in a loop, executes the critical section, and releases the same monitors in a loop, it would be formally correct. That is assuming that the array never gets modified in-between.

But as said, there is little gain in crafting such bytecode, as the JVM won’t optimize such uncommon code at runtime and still consumes stack space for every acquisition.


¹ well, there’s the mentioned sun.misc.Unsafe, but code using it doesn’t count as “ordinary Java code” and is even less portable than the discussed handcrafted bytecode.

Holger
  • 285,553
  • 42
  • 434
  • 765