0

My objective is to avoid thread deadlock or starvation. I have the following sample code for using ReentranLocks:

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m1() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
       // ... start doing the calculations here ...
     } finally {
       //Do not release the lock here, instead, release it in m2()
     }
   }
   public void m2() { 
     try {
       // ... method body
       // ... continue doing the calculations here
     } finally {
       lock.unlock()
     }
   }

 }

I know I can use tryLock() with a timeout, but I am thinking also to ensure it will be unlocked no matter what as the lock will start in m1() and will be unlocked in m2(). How to ensure it will be unlocked say after 3 seconds no matter what, as soon as I start the lock in m1()?

For the above to be successful, ie. without sending unlock request after 3 seconds, the caller or the user of the JavaBean must ensure calling m1() and then m2() immediately. This is a restriction I want to avoid, and if the programmer forgets to do that, it might result in spending a long time troubleshooting this issue, which is, why the system is getting in a deadlock.

Thoughts:

I am thinking to use Scheduled Tasks and Timers, will that work?

tarekahf
  • 738
  • 1
  • 16
  • 42
  • Can you explain more why you could noy use trylock? Because as far I know if you unlock before timeout exceed it will unlock no matter what – YJR Sep 26 '22 at 18:16
  • I want to be 100% the lock will be released if something goes wrong after the lock is acquired, it might cause a deadlock. I simply want to unlock the lock after sometime after 3 seconds using a method similar to JavaScript `setTimeout(()=> if (lock.isLocked()) lock.unlock, 3000)` – tarekahf Sep 26 '22 at 18:41

1 Answers1

1

Only the thread holding the lock may release it. That thread could track how long it has held the lock, and release it after the prescribed interval. Alternatively, another thread could wait for the prescribed interval, then signal the owner thread to stop its work and release the lock. This might be through interruption or another condition.

In either case, the lock-holding thread needs to be written to support the timeout mechanism, stop its work, and release the lock. Another thread can't forcibly revoke its lock.


You could do something hokey looking at the time the lock has been held. This has different failure modes that failing to unlock a real lock; I feel like it has more potential to cause damage, so I wouldn't use it personally, but I don't know your circumstances. (I would just log the lock() with a note to expect a corresponding unlock() log in order to aid troubleshooting, and then review my code carefully.)

Because the system time can have discontinuities and is not monotonic, the lock might be held (much) more or less than the specified time.

Here is an (untested) class intended to act like a lock that can only be held for a specified time:

final class PseudoLock {

    private final Object lock = new Object();

    private final Clock clock;

    private final long limit;

    private final TimeUnit unit;

    private Instant acquired;

    PseudoLock(Clock clock, long limit, TimeUnit unit) {
        this.clock = Objects.requireNonNull(clock);
        if (limit < 1) throw new IllegalArgumentException();
        this.limit = limit;
        this.unit = Objects.requireNonNull(unit);
    }

    void acquire() throws InterruptedException {
        synchronized (lock) {
            Instant now = Instant.now(clock);
            while (acquired != null) {
                long delay = limit - acquired.until(now, unit.toChronoUnit());
                if (delay > 0) {
                    unit.timedWait(lock, delay);
                    now = Instant.now(clock);
                } else {
                    break;
                }
            }
            acquired = now;
        }
    }

    void release() {
        synchronized (lock) {
            acquired = null;
            lock.notify();
        }
    }

}

You would use it like this:

class X {

   private final PseudoLock lock = 
     new PseudoLock(Clock.systemUTC(), 3L, TimeUnit.SECONDS);

   public void m1() { 
     lock.acquire();  // block until condition holds
     // ... method body
     // ... start doing the calculations here ...
   }

   public void m2() { 
     try {
       // ... method body
       // ... continue doing the calculations here
     } finally {
       lock.release();
     }
   }

}
erickson
  • 265,237
  • 58
  • 395
  • 493
  • I need a sample code please. – tarekahf Sep 26 '22 at 18:43
  • 2
    @tarekahf That's not how this site works. If that's what you require, let's close your question as it's off-topic. – erickson Sep 26 '22 at 18:46
  • I mean, how to modify the code snippet I provided to achieve what I want. If what I am looking for is not possible, then do mention that. If possible, then give me at least some pointers using some Java keywords and utilities without using sample code. If you cannot, please ignore my request, as I am not expecting anyone to provide a sample code. – tarekahf Sep 26 '22 at 19:14
  • @tarekahf It would be tough to illustrate with your example because there is no general way to release the lock; aborting the locked action is highly dependent on what you are actually doing in the "method body" blocks in your sample. Are you doing I/O? What sort of channel? Iterating over some in-memory data structure? It could be anything, and finding the right way to detect it's time to abort and cleaning up the operation depends heavily on what it is you are doing. – erickson Sep 26 '22 at 20:09
  • It's complex series of calculations on a relatively large JSON object, but it won't last more than 1 second, however, there are hundreds of threads running concurrently using the Calculation class. The class is implemented using singleton Spring JavaBean, so all threads use the same shared class variables. The first method will do the calculations, and the 2nd method will fetch the results from the private properties. If the caller forgets to run the 2nd method the system will get into a deadlock, which I am trying to avoid. – tarekahf Sep 26 '22 at 20:15
  • Why is the unlock in `m2()`? It should be in the finally block of `m1()`. Does `m2()` spend a lot of time after its `unlock()` call before returning, and you are trying to reduce contention by unlocking earlier? – erickson Sep 26 '22 at 20:22
  • The first method will update the private properties of the calculations and return the result as a big json object in string format. The second method will perform a get for the final value of the calculations as numeric field. I want to avoid multiple threads overlapping among each other. – tarekahf Sep 27 '22 at 18:30
  • @tarekahf Okay, that's a little hard to understand, but what I got from it is that you don't want another thread to execute while the second method is "performing a get for the final value." That is, the critical section (mutex) should include all of `m2()`. So, my question remains: why is the `unlock()` in `m2()`? Why not put it in the `finally` of `m1()`? – erickson Sep 27 '22 at 19:10
  • Because by the time thread 'T1' is finished with `m1()`, then another thread `T2` might have started and updated the same field using `m1()`, and then `T1` may read the value of `T2` instead. Does that make sense? – tarekahf Sep 29 '22 at 18:14
  • Ah, I think you mean that `m2()` is not called from `m1()`; instead, some higher level caller calls `m1()`, which returns, then calls `m2()`. Is that it? If so, I would say it's that caller that should manage the lock. Acquiring the lock, then immediately entering a `try-finally` to do the work and release the lock is idiomatic for a reason. – erickson Sep 29 '22 at 18:25
  • This is not possible at my end or may involve complex work. The invocation for the JavaBean at my end happens in a Workflow Step which doesn't have a feature to invoke the lock. It's much simple to submit an unlock request in `m1()` to be executed after 3 seconds and the problem is solved. – tarekahf Sep 29 '22 at 18:30
  • What do you mean by "submit an unlock request"? – erickson Sep 29 '22 at 18:32
  • I mean run the command to unlock after 3 seconds. – tarekahf Sep 30 '22 at 19:13
  • @tarekahf It's impossible to schedule an unlock. As I explained, only the owner of the lock can release it. Another thread, executing on a timer, can't call `unlock()`. If you can't use the lock idiomatically, your best option is probably good logging around lock and unlock to aid troubleshooting a deadlocked system. – erickson Oct 01 '22 at 02:12
  • Thanks a lot for the answer. It seems that if this is not a feature in Java then it's complex to implement. I am thinking it's follow you simple advise and use tryLock instead, and log an error if the lock is not acquired. – tarekahf Oct 01 '22 at 19:29