Locks
自動クローズ可能ですか?つまり、次の代わりに:
Lock someLock = new ReentrantLock();
someLock.lock();
try
{
// ...
}
finally
{
someLock.unlock();
}
...言ってもいい:
try (Lock someLock = new ReentrantLock())
{
someLock.lock();
// ...
}
... Java 7では?
Locks
自動クローズ可能ですか?つまり、次の代わりに:
Lock someLock = new ReentrantLock();
someLock.lock();
try
{
// ...
}
finally
{
someLock.unlock();
}
...言ってもいい:
try (Lock someLock = new ReentrantLock())
{
someLock.lock();
// ...
}
... Java 7では?
私はこれを自分で行うことを検討していて、次のようなことをしました:
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
}
}
}
いいえ、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();
// ...
}
ただし、古い構文を使用する方がよいと思います。ロックロジックを完全に表示する方が安全です。
汎用は、try-with-resourcesステートメントに必要なインターフェースReentrantLock
を実装するものも提供するものもありません。ただし、この機能AutoCloseable
を提供するので、この概念はJavaAPIにとって完全に異質ではありません。FileChannel.lock()
これまでに示した回答は、ロック呼び出しごとに不要なオブジェクトを作成する、エラーが発生しやすいAPIを公開する、ロックを取得した後、try-finalに入る前に失敗するリスクなど、いくつかの問題がある解決策を共有しています。
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;
}
}
ラムダを使用したリーナーJava8ソリューション:
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
CRに関する私の質問も参照してください。
Runnable
ロックを使用してtry-with-resourceステートメントを使用するよりも、ロックとaを使用する単純なutilメソッドの方が優れていると思います。
このような:
public static void locked(Lock lock, Runnable r) {
lock.lock();
try {
r.run();
} finally {
lock.unlock();
}
}
使用例:
locked(lock, () -> {
// Do your stuff
});
利点:
不利益
Runnable
インスタンスは呼び出しごとに割り当てられますが、他のソリューションのいくつかでは回避されています。しかし、これはほとんどすべての場合に重要ではありません。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();
}
}
user2357112の賢明なアドバイスを考慮に入れて:
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
}
@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();
}
}
ThreadLocal
これは、ロック要求ごとのルックアップを犠牲にして、うまく機能し、非常に効率的な別のソリューションです。このソリューションは、AutoCloseable
パーツ/ラッパーをキャッシュし、スレッドごとに再利用します。
まず、多くのインスタンスを持つResourceLock
法線のラッパークラスがあります。Lock
これは私たちが再利用したい部分です。ラッパーはLock
インターフェイスを実装するため、通常のように動作しますLock
が、自動で閉じることができます。
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();
}
}
再利用可能な形式では、次のように使用するだけです。
try (ResourceLock ignore = new ResourceLock(rwl.writeLock())) {
// Resource locked in here
}
ResourceLock
または、スレッドごとにオブジェクトを再利用できるキャッシュ対応ラッパーを追加することもできます。
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;
}
}
これで、自動で閉じることができる各ロックを再利用できます。
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
}
またReadWriteLock
、より複雑なロックのニーズに対応するバリアントもあります。インターフェイスを実装しているため、次のReadWriteLock
ような複雑なロック戦略を使用できるため、より用途が広くなりますtryLock
。
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
}
このソリューションが、リソースリリースハンドラーを再利用するシングルおよびマルチロック戦略に役立つことを願っています。
skoskavの優れた答えを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();
}
}
使用法:
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;
}
}
}