简体   繁体   English

锁可以自动关闭吗?

[英]Are Locks AutoCloseable?

Are Locks auto-closeable? Locks可以自动关闭吗? That is, instead of:也就是说,而不是:

Lock someLock = new ReentrantLock();
someLock.lock();
try
{
    // ...
}
finally
{
    someLock.unlock();
}

...can I say: ...我能说......么:

try (Lock someLock = new ReentrantLock())
{
    someLock.lock();
    // ...
}

...in Java 7? ...在 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.不, Lock接口(或ReentrantLock类)都没有实现AutoCloseable接口,这是与新的 try-with-resource 语法一起使用所必需的。

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.通用ReentrantLock既不实现也不提供任何实现 try-with-resources 语句所需的AutoCloseable接口的东西。 The concept isn't completely foreign to the Java API though, as FileChannel.lock() offers this functionality.不过这个概念对于 Java API 来说并不是完全陌生的,因为FileChannel.lock()提供了这个功能。

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.到目前为止给出的答案共享存在一些问题的解决方案,例如在每次锁定调用时创建一个不必要的对象、暴露容易出错的 API 或在获取锁定之后但在进入 try-finally 之前失败的风险。

Java 7 solution: 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;
    }
}

Leaner Java 8 solution using a lambda:使用 lambda 的精简Java 8解决方案:

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. try-with-resource适用于在try-block离开时创建和销毁的资源。 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 .这就是它们不是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.正如其他人已经建议的那样,可以使用try-with-resource块创建和销毁包装器,并在创建和销毁时进行锁定和解锁。

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 .另请参阅我关于 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.我认为使用锁和Runnable的简单 util 方法比使用带锁的 try-with-resource 语句更好。

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.没有为 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.为每个调用分配一个Runnable实例,这是其他一些解决方案避免的。 But this is insignificant in almost all cases.但这在几乎所有情况下都是微不足道的。
  • Only works if you can use Java 8.仅当您可以使用 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:考虑到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;
  }

}

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 .CloseableLock实现java.util.concurrent.locks.Lock可能很有趣。

Building on Stephen's answer and user2357112's idea, I have written the following class.基于斯蒂芬的回答和 user2357112 的想法,我编写了以下课程。

The MyLock class itself is not closeable itself, to force users of the class to call get(). 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();
    }
}

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:将@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();
    }
} 

Here is another solution that works great and is super efficient at the expense of a ThreadLocal lookup per lock request.这是另一种效果很好的解决方案,它以每个锁请求的ThreadLocal查找为代价,非常高效。 This solution caches the AutoCloseable part/wrapper and reuses it on a per-thread basis.此解决方案缓存AutoCloseable部件/包装器并在每个线程的基础上重用它。

First we have a wrapper class ResourceLock around a normal Lock that we will have many instances of.首先,我们有一个包装类ResourceLock围绕着一个普通的Lock ,我们将拥有许多实例。 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:包装器实现了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();
    }
}

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.或者我们可以添加一个具有缓存能力的包装器,这将使我们能够重用每个线程的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;
    }
}

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.还有一个ReadWriteLock变体,用于更复杂的锁定需求。 It implements the ReadWriteLock interface so its more versatile as you can use complex locking strategies such as tryLock etc:它实现了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
}

Hope this solution helps for single and multi lock strategies with re-using the resource release handlers.希望这个解决方案有助于单锁和多锁策略,重用资源释放处理程序。

Extending skoskav 's excellent answer to ReadWriteLock :扩展skoskavReadWriteLock的出色回答:

CloseableLock.java: CloseableLock.java:

public interface CloseableLock extends AutoCloseable
{
    /**
     * Release the lock.
     */
    @Override
    void close();
}

ReadWriteLockAsResource: 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;
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM