Search code examples
javalocking

ReentrantLock - should we check who holds the lock while unlocking in finally block?


A simple snippet get stuck often when delay is slightly high:

public String getToken(String userId, ReentrantLock lock) throws InterruptedException {
    try {
        // lock is in a cache of ConcurrentHashMap with key of userId
        if (!lock.tryLock(LOCK_TIMEOUT_SEC, TimeUnit.SECONDS)) {
            throw new AppException("Could not get lock in " + LOCK_TIMEOUT_SEC + "s"); // 10 sec
        }
        String token = fetchNewToken(userId); // call external endpoint
        lock.unlock();
        return token;
    } finally {
        while (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

Now the situation is it often locks 10 seconds when fetchNewToken has slightly higher delay(like 1 sec) and throws a lot of exceptions. Formerly it unlocks without checking, I changed the finally part but now I am not sure.

Should I check the lock owner in the finally block? If other threads are holding it, I guess I should not unlock?

Examples I found never checks the owner. So the doubt.


Solution

  • If other threads are holding it, I guess I should not unlock?

    Definitely not.

    You're overcomplicating it. Don't unlock unless you know you've acquired it. Move the try-finally block. You don't need 2 calls to unlock either. The finally block will do it for all cases; that's why it exists.

    public String getToken(String userId, ReentrantLock lock) throws InterruptedException {
        // lock is in a cache of ConcurrentHashMap with key of userId
        if (!lock.tryLock(LOCK_TIMEOUT_SEC, TimeUnit.SECONDS)) {
            throw new AppException("Could not get lock in " + LOCK_TIMEOUT_SEC + "s"); // 10 sec
        }
        try {
            return fetchNewToken(userId); // call external endpoint
        } finally {
            lock.unlock();
        }