smellyarmpits
smellyarmpits

Reputation: 1120

Java lock and unlock in different methods. How to try/finally?

I'm trying to figure out what is the best way to use the try/finally with locks.

When I have lock() and unlock() in the same place, I just use the try/finally block as the JavaDoc also suggests:

lock.lock();
try{
  // do something
} finally {
  lock.unlock();
}

I am wondering what's the best practice to use the try/finally when the lock() and unlock() calls are in separate methods.

For example, consider the following:

public class X {
  private ReentrantLock lock = new ReentrantLock();

  public void pickUp(){
    lock.lock();
    // do something
  }

  public void putDown(){
    // do something
    lock.unlock();
  }
}

What I would do is to put the try/finally block on the upper level, that is, whenever I call the methods pickUp() and putDown(). For example, inside of the run() method:

// class code
X x = new X();

public void run(){
  try{
    x.pickUp();
    // do something
  } finally {
    x.putDown();
  }
}

Is this the proper way?

Thank you very much.

Upvotes: 4

Views: 2561

Answers (1)

bowmore
bowmore

Reputation: 11310

This is actually a very good candidate to apply the execute around method pattern.

If a client of your class needs to do something with a resource that needs proper initialization and/or proper cleanup, you can leave the cleanup in the hands of the client, and hope that documentation and fail fast behavior nudges the client into properly handling the resource.

The execute around method pattern delas with this conundrum by taking the initialization and cleaup out of the client's hands again. Basically you introduce a method that does the initialization and cleanup of the resource properly, and in between hands it off to a Consumer of that resource.

Ideally you can even completely encapsulate the methods that do the initialization and cleanup.

As an example, based on your example code :

private ReentrantLock lock = new ReentrantLock();

private void pickUp() { // now private
  lock.lock();
  // do something
}

private void putDown() { // now private
  // do something
  lock.unlock();
}

public void use(Consumer<X> consumer) {
  try {
    pickUp();
    consumer.accept(this);  // client gets its hands on the properly initialized resource.
  } finally {
    putDown();
  }
}

The client can still do messy things with the resource, such as using it outside of the consumer, after cleanup, but this is already harder to do. And the hard part, remembering to proper init and cleanup, is no longer the client's concern.

Upvotes: 1

Related Questions