Search code examples
javamultithreadingbooleanlocking

Why would a call to Thread.holdsLock() return false in a situation where it should return true?


I've got a Java program that features a main class that manages several identical daemon threads. The main class has a static ReentrantLock that the daemon threads access. The main class creates the threads, starts them, and interrupts them depending on user input. The other threads (which are all instances of the same class) are coded to terminate upon interruption after first releasing the lock if they own it. No other code interacts with the lock (not even in the main class). The way I have the daemon class coded, it is impossible for the daemon threads to do anything without first acquiring the lock. If Thread.holdsLock() wasn't returning the wrong value, it would be impossible to re-obtain the lock without first releasing it (i.e. the hold count would always be either one or zero). I'm running an Eclipse JavaFX Maven project.

Here's an pseudocode example of the classes' run() method:

// setup
while(!exitCondition)
  try {
    myLock.lockInterruptibly();
    // do stuff
    if(exitCondition) tellMain(); // this causes main to interrupt all threads
    // sleep for a bit
  } catch (InterruptedException e) {
    // clean up
    break;
  } finally {
     if(Thread.holdsLock(myLock)) myLock.unlock();
  }
}
// termination message

If I remove the if statement, every thread that doesn't own the lock throws an IllegalMonitorStateException. I've been staring at this for hours and do not see any way for Thread.holdsLock() to return false if the thread that called it owns the lock. The method managing the threads does not interact with the lock in any way. How is this happening?


Solution

  • There are 2 unrelated locking systems in java. You're mixing one with the other and that does not work. There's java.util.concurrent.Lock and all related business, and utterly unrelated to it, 'team synchronized', which includes the features:

    • Thread.holdsLock <-- there's your problem
    • obj.wait() + obj.notify / obj.notifyAll
    • synchronized, the keyword.

    You're mixing and matching which does not work. If you want to know if a thread holds a juc lock, you can't do that. It's not part of juc lock's API.

    synchronized more closely resembles what the OS is doing, juc lock is somewhat more abstract. Especially in light of fibers (the Loom project, new in JDK21), you probably want juc locks. But, if you need holdsLock, you either write that yourself (you can just wrap a lock and register which thread holds it), or use low-level ('team synchronized') locks.

    holdsLock says 'false' because that thread does not hold the synchronized lock. The only way to get Thread.holdsLock(foo) to return true is with synchronized (foo) { / * now Thread.holdsLock(foo); will be true */ }.

    If you want to unlock a juc lock, just do so, and catch the IllegalStateException that occurs if you never held it.

    ... but ...

    If your lock is specifically j.u.c.ReentrantLock, and it pobably is, then, you do have a method you can use. Replace Thread.holdsLock(myLock) with myLock.isHeldByCurrentThread(). Note that your myLock param/field/var needs to be typed ReentrantLock. RL has that method; j.u.c.Lock does not (the interface tries to apply to as many implementations as possible, and perhaps not all implementations want to / are capable of tracking which thread actually holds it).