JavaMemoryModel: october JMM spec seems to break java's threading model

From: Lanchon (lanchon@hotmail.com)
Date: Wed Nov 05 2003 - 15:06:29 EST


hello everybody, i've just found the spec and this list. i'm a little late but better late than never i guess...

although java doesn't have fairness guarantees for good reasons, i think it intends to have a kind of execution guarantee, something like "at least one thread of the set of currently runnable threads will make progress" (is it written somewhere?). something that guarantees that code like this will eventually finish:

final boolean[] x = new boolean[1];
synchronized (x)
{
  new Thread()
  {
    public void run()
    {
      synchronized (x)
      {
        x[0] = true;
        x.notify();
      }
    }
  }
  .start();
  while (!x[0]) x.wait();
}

however this guarantee looses its meaning under the proposed JMM spec. spurious wakeups plus the absence of a fairness guarantee mean that the while can loop forever. it seems impossible to make any multithreaded java program that uses wait/notify that is guaranteed to run.

ok, so the subject of this posting is maybe a bit too sensacionalist. a better one would have been "one last plea against spurious wakeups and its mischievous friend re-notification" but i guess no one would have read it then...

regarding re-notification, i understand JVMs try to prevent loss of notifications by re-notifying the monitor after an interrupted thread wakes up. this however is not proposed spec-compliant, because the waiting set of the monitor may at wakeup time have new threads that weren't present when the original notify was done. in the extreme a thread could even end up notifying itself if after the notify it adds itself to the wait set.

the only way out of this seems to be that JVMs to do a notifyAll after an interrupted thread wakes up, which is ugly. is this how current JVMs behave? if not, proposing an executively unimplementable spec to replace an older executively unimplementable spec seems to be a bad idea.

i think maybe "notifications cannot be lost due to interrupts" should be left out from the spec, maybe explicitly permitting the re-notification after interrupt behaviour instead. or maybe the part that defines notify that says "if m's wait set is not empty, a thread u that is a member of m's current wait set is selected and removed from the wait set" should be changed to something that expresses that a notify will schedule the selection and removal of a thread from a wait set for later execution (but only if the wait set is not empty, otherwise a problem would arise if you notify a monitor with an empty wait set: the notify could be postponed indefinetly, you could get the notify in a wait that's invoked much latter).

i'm writting all this because i'd like to see spurious wakeups out of the spec if possible. i understand the JVM reengineering efforts would be too great, so here are my 2c:

i see from reading some postings here that re-notification is a cause for spurious wakeups, but is it the only one? though no other cause is cited, i guess definetly NOT. but if it is, then scrap the no-notify-loss guarantee, re-notifications and spurious wakeups (we should be so lucky!).

if not, what other sources of spurious wakeups are there in the Hard Real World JVM implementors talk so much about? is there any way to restrict the validity of spurious wakeups only to certain situations?

i guess a lot of code that relay on notifyAll will be broken by spurious wakeups, and i'd like it to be protected somehow by the spec if possible. this is a REAL piece of code i made a while back (i'd already heard about spurious wakeups). it's from a joineable object pool, but a reusable runnable i wrote had the exact same code in it. (sic, i wrote FUTURE BUG naively thinking JVMs where then well behaved, silly me...)

/* FUTURE BUG ??? (Spurious wakeups have been proposed for the Java 1.5 memory model.)
// TODO: Detect whether a join or a timeout occurred.
public void join() throws InterruptedException { join(0); }
public void join(long timeout) throws InterruptedException
{
  synchronized (lock) { if (block) lock.wait(timeout); } // FUTURE BUG ???
}
*/

public boolean join() throws InterruptedException
{
  synchronized (lock) { while (block) lock.wait(); }
  return true;
}

public boolean join(long timeout) throws InterruptedException
{
  if (timeout <= 0)
  {
    if (timeout == 0) return join();
    else throw new IllegalArgumentException();
  }
  synchronized (lock)
  {
    if (block)
    {
      long timeoutTime = System.currentTimeMillis() + timeout;
      lock.wait(timeout);
      while (block);
      {
        long thisTimeout = timeoutTime - System.currentTimeMillis();
        if (thisTimeout <= 0) return false;
        lock.wait(thisTimeout);
      }
    }
  }
  return true;
}

the code bloat is terrible and error prone. or maybe i should let join(millis) return when it likes to and screw the user, like i guess JVMs do with wait now? well that's it! not even document it! why should i? maybe 8 years from now i'd start to discuss the 'feature', and of course not fix it. i'd say, leave me alone people! your code used this library for years, there's no point in fixing it NOW, i mean given that your code sort of works its obvious you have WORKED AROUND THIS SOMEHOW BY NOW!

but seriously, i've seen many other examples like this. it'd be great if something could be done to salvage common code like this by sensibly restricting when spurious wakeups can occur in the spec, supposing these instances are restricted in the real world to start with of course. maybe the problem of the validity of "indefinite postponement" of any usefull processing introduced by the new spec, the first problem that motivated this mail, can be solved in this way.

cheers!

ps. i dislike the overloading of semantics of final fields to make them thread safe even in the presence of data races. for many reasons i favor a source-level construct declaring if, and maybe even when, freezes should be done. is there any sense in writting my toughts or is it just too late?

pps. in appendix D.1:

"A class loader is considered reachable if any instance of a class loaded by that loader is reachable. A class object is considered reachable if the class loader that loaded it is reachable."

should read:

"A class loader is considered reachable if any instance of a class loaded by that loader or any class object loaded by that loader is reachable. A class object is considered reachable if the class loader that loaded it is reachable."

...i guess.

-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:52 EDT