tarekahf
tarekahf

Reputation: 1002

How to release Java ReentrantLock after sometime no matter what

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?

Upvotes: 0

Views: 996

Answers (2)

LagSeeing
LagSeeing

Reputation: 83

how about use Semaphore with ScheduledExecutorService?

In the javadoc

A semaphore initialized to one, and which is used such that it only has at most one permit available, can serve as a mutual exclusion lock. This is more commonly known as a binary semaphore, because it only has two states: one permit available, or zero permits available. When used in this way, the binary semaphore has the property (unlike many java.util.concurrent.locks.Lock implementations), that the "lock" can be released by a thread other than the owner (as semaphores have no notion of ownership). This can be useful in some specialized contexts, such as deadlock recovery.

The only limitation is that Semaphore can not reentrant.

Or you can think a method to stop the thread instead of release the lock

Extra Sources you may find useful: unlocking-lock-owned-by-another-thread-java

Upvotes: 0

erickson
erickson

Reputation: 269857

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();
     }
   }

}

Upvotes: 1

Related Questions