Are Locks AutoCloseable?
Are Locks
auto-closeable? That is, instead of:
Lock someLock = new ReentrantLock();
someLock.lock();
try
{
// ...
}
finally
{
someLock.unlock();
}
...can I say:
try (Lock someLock = new Reentrant开发者_运维知识库Lock())
{
someLock.lock();
// ...
}
...in Java 7?
I was looking into doing this myself and did something like this:
public class CloseableReentrantLock extends ReentrantLock implements AutoCloseable {
public CloseableReentrantLock open() {
this.lock();
return this;
}
@Override
public void close() {
this.unlock();
}
}
and then this as usage for the class:
public class MyClass {
private final CloseableReentrantLock lock = new CloseableReentrantLock();
public void myMethod() {
try(CloseableReentrantLock closeableLock = lock.open()) {
// locked stuff
}
}
}
No, neither the Lock
interface (nor the ReentrantLock
class) implement the AutoCloseable
interface, which is required for use with the new try-with-resource syntax.
If you wanted to get this to work, you could write a simple wrapper:
public class LockWrapper implements AutoCloseable
{
private final Lock _lock;
public LockWrapper(Lock l) {
this._lock = l;
}
public void lock() {
this._lock.lock();
}
public void close() {
this._lock.unlock();
}
}
Now you can write code like this:
try (LockWrapper someLock = new LockWrapper(new ReentrantLock()))
{
someLock.lock();
// ...
}
I think you're better off sticking with the old syntax, though. It's safer to have your locking logic fully visible.
The general-purpose ReentrantLock
neither implements nor provides anything that implements the AutoCloseable
interface necessary for a try-with-resources statement. The concept isn't completely foreign to the Java API though, as FileChannel.lock()
offers this functionality.
The answers given so far share solutions that have some issues, such as creating an unnecessary object on each lock call, exposing an error-prone API or risk failing after the lock is acquired but before the try-finally is entered.
Java 7 solution:
public interface ResourceLock extends AutoCloseable {
/**
* Unlocking doesn't throw any checked exception.
*/
@Override
void close();
}
public class CloseableReentrantLock extends ReentrantLock {
private final ResourceLock unlocker = new ResourceLock() {
@Override
public void close() {
CloseableReentrantLock.this.unlock();
}
};
/**
* @return an {@link AutoCloseable} once the lock has been acquired.
*/
public ResourceLock lockAsResource() {
lock();
return unlocker;
}
}
Leaner Java 8 solution using a lambda:
public class CloseableReentrantLock extends ReentrantLock {
/**
* @return an {@link AutoCloseable} once the lock has been acquired.
*/
public ResourceLock lockAsResource() {
lock();
return this::unlock;
}
}
Demonstration:
public static void main(String[] args) {
CloseableReentrantLock lock = new CloseableReentrantLock();
try (ResourceLock ignored = lock.lockAsResource()) {
try (ResourceLock ignored2 = lock.lockAsResource()) {
System.out.println(lock.getHoldCount()); // 2
}
}
System.out.println(lock.getHoldCount()); // 0
}
The try-with-resource
works well for resources which are created and destroyed when try-block
is left. It does not work for resources which need to be kept alive. Locks are not created and destroyed upon each usage. They are kept alive and just locked and unlocked. This is why they are not AutoClosable
.
As others already suggested a wrapper can be used to be created and destroyed by the try-with-resource
block and to do the locking and unlocking upon creation and destruction.
There's no perfect solution, unless you ignore the allocation costs (most application programmers can, but the lock library writers can not). Then you can use a wrapper
@RequiredArgsConstructor(access=AccessLevel.PRIVATE)
public final class MgLockCloseable implements AutoCloseable {
public static MgLockCloseable tryLock(Lock lock) {
return new MgLockCloseable(lock.tryLock() ? lock : null);
}
public static MgLockCloseable lock(Lock lock) {
lock.lock();
return new MgLockCloseable(lock);
}
@Override public void close() {
if (isLocked()) {
lock.unlock();
}
}
public boolean isLocked() {
return lock != null;
}
@Nullable private final Lock lock;
}
in this construct
try (LockCloseable lockCloseable = LockCloseable.lock(lock)) {
doSomethingUnderLock();
} // automatic release
See also my question on CR.
I think a simple util method which takes a lock and a Runnable
is better than using the try-with-resource statement with locks.
Like this:
public static void locked(Lock lock, Runnable r) {
lock.lock();
try {
r.run();
} finally {
lock.unlock();
}
}
Usage example:
locked(lock, () -> {
// Do your stuff
});
Advantages:
- There is no dummy variable created for the try-with-resource.
- I think it is very clear.
Disadvantage
- A
Runnable
instance is allocated for each calls, something that some of the other solutions avoid. But this is insignificant in almost all cases. - Only works if you can use Java 8.
public class AutoCloseableLockWrapper implements AutoCloseable, Lock{
private final Lock lock;
public AutoCloseableLockWrapper(Lock l) {
this.lock = l;
}
@Override
public void lock() {
this.lock.lock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
lock.lockInterruptibly();
}
@Override
public boolean tryLock() {
return lock.tryLock();
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return lock.tryLock(time,unit);
}
@Override
public void unlock() {
lock.unlock();
}
@Override
public Condition newCondition() {
return lock.newCondition();
}
@Override
public void close() {
this.lock.unlock();
}
}
Taking user2357112's shrewd advice into account:
public class CloseableLock {
private class Unlocker implements AutoCloseable {
@Override
public void close() throws Exception {
lock.unlock();
}
}
private final Lock lock;
private final Unlocker unlocker = new Unlocker();
public CloseableLock(Lock lock) {
this.lock = lock;
}
public AutoCloseable lock() {
this.lock.lock();
return unlocker;
}
}
Use:
CloseableLock lock = new CloseableLock(new ReentrantLock());
try (AutoCloseable unlocker = lock.lock()) {
// lock is acquired, automatically released at the end of this block
} catch (Exception it) {
// deal with it
}
Could be interesting to make CloseableLock
implement java.util.concurrent.locks.Lock
.
Building on Stephen's answer and user2357112's idea, I have written the following class.
The MyLock class itself is not closeable itself, to force users of the class to call get().
public class MyLock {
public class Session implements AutoCloseable {
@Override
public void close() {
freeLock();
}
}
private ReentrantLock reentrantLock = new ReentrantLock();
public Session get() {
reentrantLock.lock();
return new Session();
}
private void freeLock() {
reentrantLock.unlock();
}
}
Here is a typical use:
MyLock myLock = new MyLock();
try( MyLock.Session session = myLock.get() ) {
// Lock acquired
}
Extending the Java8 solution of @skoskav to ReentrantReadWriteLock:
public interface ResourceLock extends AutoCloseable {
/**
* Unlocking doesn't throw any checked exception.
*/
@Override
void close();
}
public class CloseableReentrantRWLock extends ReentrantReadWriteLock {
/**
* @return an {@link AutoCloseable} once the ReadLock has been acquired
*/
public ResourceLock lockRead() {
this.readLock().lock();
return () -> this.readLock().unlock();
}
/**
* @return an {@link AutoCloseable} once the WriteLock has been acquired.
*/
public ResourceLock lockWrite() {
this.writeLock().lock();
return () -> this.writeLock().unlock();
}
}
Here is another solution that works great and is super efficient at the expense of a ThreadLocal
lookup per lock request. This solution caches the AutoCloseable
part/wrapper and reuses it on a per-thread basis.
First we have a wrapper class ResourceLock
around a normal Lock
that we will have many instances of. This is the part we want to reuse. The wrapper implements the Lock
interface so it behaves like a normal Lock
but one that can be auto closed:
public class ResourceLock implements AutoCloseable, Lock {
private Lock lock;
public ResourceLock(Lock lock) {
this(lock, true);
}
public ResourceLock(Lock lock, boolean eagerLock) {
this.lock = lock;
if (eagerLock) {
lock.lock();
}
}
public void lock() {
lock.lock();
}
public void lockInterruptibly() throws InterruptedException {
lock.lockInterruptibly();
}
public Condition newCondition() {
return lock.newCondition();
}
ResourceLock setLock(Lock lock) {
this.lock = lock;
return this;
}
public boolean tryLock() {
return lock.tryLock();
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return lock.tryLock(time, unit);
}
public void unlock() {
lock.unlock();
}
@Override
public void close() {
lock.unlock();
}
}
In none reusable form you would simply use it like this:
try (ResourceLock ignore = new ResourceLock(rwl.writeLock())) {
// Resource locked in here
}
Or we can add a caching capable wrapper which will let us reuse ResourceLock
objects per thread.
public class ResourceLockCache {
private final Lock lock;
private final Supplier<ResourceLock> cachingStrategy;
public ResourceLockCache(Lock lock) {
this.lock = lock;
final ThreadLocal<ResourceLock> strategy = new ThreadLocal<ResourceLock>() {
@Override
protected ResourceLock initialValue() {
return new ResourceLock();
}
};
this.cachingStrategy = strategy::get;
}
public ResourceLockCache(Lock lock, Supplier<ResourceLock> cachingStrategy) {
this.lock = lock;
this.cachingStrategy = cachingStrategy;
}
public ResourceLock getAsResource() {
final ResourceLock activeLock = cachingStrategy.get();
activeLock.setLock(lock);
return activeLock;
}
public ResourceLock getAsResourceAndLock() {
final ResourceLock activeLock = cachingStrategy.get();
activeLock.setLock(lock);
activeLock.lock();
return activeLock;
}
}
Now we can use reuse each auto-closeable lock:
ResourceLockCache rlc = new ResourceLockCache(new ReentrantLock());
// Or this to change caching strategy to new object per lock
ResourceLockCache rlc2 = new ResourceLockCache(new ReentrantLock(), ResourceLock::new);
try (ResourceLock ignore = rlc.getAsResourceAndLock()) {
// Resource locked in here
}
Also have a ReadWriteLock
variant for more complex locking needs. It implements the ReadWriteLock
interface so its more versatile as you can use complex locking strategies such as tryLock
etc:
public class ResourceRWLockCache implements ReadWriteLock {
private final ReadWriteLock rwl;
private final Supplier<ResourceLock> cachingStrategy;
public ResourceRWLockCache(ReadWriteLock rwl) {
this.rwl = rwl;
final ThreadLocal<ResourceLock> strategy = new ThreadLocal<ResourceLock>() {
@Override
protected ResourceLock initialValue() {
return new ResourceLock();
}
};
this.cachingStrategy = strategy::get;
}
public ResourceRWLockCache(ReadWriteLock rwl, Supplier<ResourceLock> cachingStrategy) {
this.rwl = rwl;
this.cachingStrategy = cachingStrategy;
}
public ResourceLock readLock() {
final ResourceLock activeLock = cachingStrategy.get();
activeLock.setLock(rwl.readLock());
return activeLock;
}
public ResourceLock readLockAndLock() {
final ResourceLock activeLock = cachingStrategy.get();
activeLock.setLock(rwl.readLock());
activeLock.lock();
return activeLock;
}
public ResourceLock writeLock() {
final ResourceLock activeLock = cachingStrategy.get();
activeLock.setLock(rwl.writeLock());
return activeLock;
}
public ResourceLock writeLockAndLock() {
final ResourceLock activeLock = cachingStrategy.get();
activeLock.setLock(rwl.writeLock());
activeLock.lock();
return activeLock;
}
}
ResourceRWLockCache rwl = new ResourceRWLockCache(new ReentrantReadWriteLock());
// Or this to change caching strategy to new object per lock
ResourceRWLockCache rwl2 = new ResourceRWLockCache(new ReentrantReadWriteLock(), ResourceLock::new);
try (ResourceLock ignore = rwl.writeLockAndLock()) {
// Resource locked in here
}
Hope this solution helps for single and multi lock strategies with re-using the resource release handlers.
Extending skoskav's excellent answer to ReadWriteLock
:
CloseableLock.java:
public interface CloseableLock extends AutoCloseable
{
/**
* Release the lock.
*/
@Override
void close();
}
ReadWriteLockAsResource:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
/**
* Enables the use of {@code try-with-resources} with {@code ReadWriteLock}.
*/
public final class ReadWriteLockAsResource
{
private final ReadWriteLock lock;
/**
* @param lock a lock
* @throws NullPointerException if {@code lock} is null
*/
public ReadWriteLockAsResource(ReadWriteLock lock)
{
if (lock == null)
throw new NullPointerException("lock may not be null");
this.lock = lock;
}
/**
* Starts a new read-lock.
*
* @return the read-lock as a resource
*/
public CloseableLock readLock()
{
Lock readLock = lock.readLock();
readLock.lock();
return readLock::unlock;
}
/**
* Starts a new write-lock.
*
* @return the write-lock as a resource
*/
public CloseableLock writeLock()
{
Lock writeLock = lock.writeLock();
writeLock.lock();
return writeLock::unlock;
}
/**
* Returns a new condition.
*
* @return a new condition
*/
public Condition newCondition()
{
return lock.writeLock().newCondition();
}
}
Usage:
public final class GuideToTheUniverse
{
private final LockAsResource lock = new LockAsResource(new ReentrantReadWriteLock());
public int answerToLife()
{
try (CloseableLock writeLock = lock.writeLock())
{
System.out.println("Look ma', no hands!");
return 42;
}
}
}
精彩评论