Locks
は自動で閉じることができますか?つまり、次の代わりに:
Lock someLock = new ReentrantLock();
someLock.lock();
try
{
// ...
}
finally
{
someLock.unlock();
}
...言ってもいい:
try (Lock someLock = new ReentrantLock())
{
someLock.lock();
// ...
}
... in Java 7?
いいえ、 Lock
インターフェース(または ReentrantLock
クラス)のどちらもAutoCloseable
インターフェースを実装していません。新しいtry-with-resource構文で使用します。
これを機能させたい場合は、簡単なラッパーを作成できます。
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();
}
}
これで、次のようなコードを記述できます。
try (LockWrapper someLock = new LockWrapper(new ReentrantLock()))
{
someLock.lock();
// ...
}
ただし、古い構文を使用したほうがよいと思います。ロックロジックを完全に表示する方が安全です。
私はこれを自分で行うことを検討していて、次のようなことをしました。
public class CloseableReentrantLock extends ReentrantLock implements AutoCloseable {
public CloseableReentrantLock open() {
this.lock();
return this;
}
@Override
public void close() {
this.unlock();
}
}
次に、これをクラスの使用法として使用します。
public class MyClass {
private final CloseableReentrantLock lock = new CloseableReentrantLock();
public void myMethod() {
try(CloseableReentrantLock closeableLock = lock.open()) {
// locked stuff
}
}
}
汎用 ReentrantLock
は、try-with-resourcesステートメントに必要なAutoCloseable
インターフェースを実装も提供もしません。 FileChannel.lock()
がこの機能を提供するため、この概念はJava APIとは完全に異質ではありません。
これまでの回答は、ロック呼び出しごとに不要なオブジェクトを作成する、エラーが発生しやすいAPIを公開する、ロックを取得した後、try-finallyに入る前に失敗するリスクなど、いくつかの問題がある解決策を共有しています。
Java 7ソリューション:
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;
}
}
ラムダを使用したリーナーJava 8ソリューション:
public class CloseableReentrantLock extends ReentrantLock {
/**
* @return an {@link AutoCloseable} once the lock has been acquired.
*/
public ResourceLock lockAsResource() {
lock();
return this::unlock;
}
}
デモンストレーション:
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
}
try-with-resource
は、try-block
が残っているときに作成および破棄されるリソースに対して適切に機能します。存続させる必要のあるリソースでは機能しません。ロックは、使用するたびに作成および破棄されることはありません。それらは存続し、ロックおよびロック解除されるだけです。これが、それらがAutoClosable
ではない理由です。
他の人がすでに示唆しているように、ラッパーはtry-with-resource
ブロックによって作成および破棄され、作成および破棄時にロックおよびロック解除を行うために使用できます。
割り当てコストを無視しない限り、完璧な解決策はありません(ほとんどのアプリケーションプログラマーはできますが、ロックライブラリライターはできません)。次に、ラッパーを使用できます
@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;
}
この構成では
try (LockCloseable lockCloseable = LockCloseable.lock(lock)) {
doSomethingUnderLock();
} // automatic release
My CRに関する質問 も参照してください。
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();
}
}
@skoskavのJava8ソリューションを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();
}
}
ser2357112の賢明なアドバイス を考慮に入れる:
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;
}
}
使用する:
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
}
CloseableLock
にJava.util.concurrent.locks.Lock
を実装させるのは興味深いかもしれません。
Stephenの答えとuser2357112のアイデアに基づいて、次のクラスを作成しました。
MyLockクラス自体は、クラスのユーザーに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();
}
}
典型的な使用法は次のとおりです。
MyLock myLock = new MyLock();
try( MyLock.Session session = myLock.get() ) {
// Lock acquired
}
ロックとRunnable
を使用する単純なutilメソッドは、ロックを使用してtry-with-resourceステートメントを使用するよりも優れていると思います。
このような:
public static void locked(Lock lock, Runnable r) {
lock.lock();
try {
r.run();
} finally {
lock.unlock();
}
}
使用例:
locked(lock, () -> {
// Do your stuff
});
利点:
不利益
Runnable
インスタンスは呼び出しごとに割り当てられますが、これは他のソリューションの一部では回避されています。しかし、これはほとんどすべての場合に重要ではありません。