Hodiya Eyal
Hodiya Eyal

Reputation: 73

Can i modify ReentrantLock so one can use it with try with resources?

I wanted to ask if this implementation is ok, or maybe there are some issues that can be later problematic.

The idea is to implement a ReentrantLock class that can be used with 'try with resources' and that means that it has to implement the AutoCloseable interface.

In addition, I wanted to ask why in the 'main' method the compiler yields that I have to add a 'catch' clause, because my close method (in MyReentrantLock) doesn't throw an exception.

import java.util.concurrent.locks.ReentrantLock;

public class Ex09ReentrantLock {

    @SuppressWarnings("serial")
    public static class MyReentrantLock extends ReentrantLock implements AutoCloseable{

        public MyReentrantLock() {
            super();
            lock();
        }

        @Override
        public void close(){        
            unlock();
        }   
    }

    public static AutoCloseable lock() {

        var locker = new MyReentrantLock(); //lock() is called in the constructor

        return locker;  //implements AutoCloseable          
    }


    //Demo 
    public static void main(String[] args) {

        //try(ReentrantLock lock = new ReentrantLock()){} = compiler error

        try(var locker = lock();){          
            //some work...          
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }           
    }
}
  1. Is this implementation ok? (call the lock in the constructor...)

  2. Why does the compiler force me to add a catch() clause if my close doesn't throw an exception?

Upvotes: 0

Views: 366

Answers (2)

DuncG
DuncG

Reputation: 15196

The AutoCloseable interface defines the use of close handler which gets called automatically at the end of a try-with-resources block:

void close() throws Exception

You can adapt any method which has a compatible definition to the signature of close() as an AutoCloseable. An easy way to do this for ReentrantLock (or Lock) is to add an interface to perform lock() and return unlock() as the close handler. This interface defines a close() with generic exception type:

interface AutoClose<T extends Exception> extends AutoCloseable {
    void close() throws T;
    
    static AutoClose<RuntimeException> lock(Lock lock) {
        lock.lock();
        return lock::unlock;
    }
}

Using the lock...unlock is then simply a try with resources call using an existing Lock or ReentrantLock:

ReentrantLock lock = new ReentrantLock();
...
try(var myLock = AutoClose.lock(lock)) {
    // critical section here
}

It is easy to extend AutoClose to handle other classes such as Semaphore:

static AutoClose<RuntimeException> acquire(Semaphore semaphore) throws InterruptedException {
    semaphore.acquire();
    return semaphore::release;
}
// example call:
try(var sem = AutoClose.acquire(semaphore)) {
     // critical section here
}

Upvotes: 0

Antoine Marques
Antoine Marques

Reputation: 1389

Locks are meant to be shared. A lock that locks on construction can only be locked using new and thus isn't shareable meaning its useless.

I believe your sample code is for illustration purpose only (but if not, it is definitely a bad idea), to help you more a real case should be given.

Guessing from what you shared, what's closest to what you expressed is the synchronized keyword.

In general, locks interactions are difficult to handle, which led to the creation of the synchronized mechanism. Using locks instead should be done in very specific situations where synchronization isn't possible.

Upvotes: 1

Related Questions