锁是否具有自动关闭功能?

53

Locks是否是自动可关闭的?也就是说,是否可以这样做:

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

我能这样说吗:

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

...在Java 7中?


3
您可以提出请求,让他们这样做。 - ratchet freak
12个回答

63

我正在研究如何做到这一点,我做了类似于这样的事情:

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
      }
   }
}

2
这就是我目前正在做的事情,它使开发变得轻松愉快。无法赞同更多。 - corsiKa
这就是正确的做法。但是,为什么不把那个方法叫做 lock() 而不是 open() 呢? - Thomas Ahle
4
因为覆盖和开启/关闭更加搭配(它们听起来像是做同样的事情),比起锁定/关闭。 - Stephen
3
为了使其正常工作,该方法需要返回对象,但是lock()是继承自ReentrantLock并且具有void返回类型。由于在不破坏继承关系的情况下无法使用lock(),因此选择open()来配合close()更加合理。 - Andrew
12
我会尽力进行翻译,同时保持原意的前提下,让内容更加易懂。以下是需要翻译的内容:"I'd consider making the lock itself not AutoCloseable, and instead have open return a separate object whose close releases the lock. Otherwise, you risk people doing try(CloseableReentrantLock closeableLock = lock) without the open call."我会考虑使锁本身不是AutoCloseable,并改为让open返回一个单独的对象,该对象的close方法释放锁。否则,你将面临人们在没有进行open调用的情况下,使用try(CloseableReentrantLock closeableLock = lock)的风险。 - user2357112
2
这是最好的解决方案,但考虑到它仍然容易出错,我可能会选择在由多个开发人员维护的大型代码库中坚持传统的(非 try-with-resource)方法。 - Bogdan Calmac

30

通用的ReentrantLock既没有实现也没有提供实现AutoCloseable接口所需的内容,该接口在使用try-with-resources语句时是必需的。尽管Java API中不存在完全陌生的概念,但FileChannel.lock()提供了这个功能。

迄今为止给出的答案都存在一些问题,例如每次锁定调用都会创建一个不必要的对象,公开容易出错的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;
    }
}

使用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;
    }
}

演示:
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
}

3
这似乎没有回答有关Java 7和AutoCloseable的问题。你的意思是要提出一个单独的问题吗? - Brian J
4
你的Java 8解决方案非常优雅。做得好!我已经在https://stackoverflow.com/a/48145269/14731上发布了一个相关的“ReadWriteLock”设计。 - Gili
1
最佳解决方案应该排在首位! - Lazar Petrovic

27
不,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();
    // ...
}

我认为你最好坚持使用旧的语法。这样可以更安全地让你的锁定逻辑完全可见。


36
为了给初学者阅读此内容的人澄清一下:在实际的代码中,您不应该每次锁定时都创建一个新的锁,因为这会使其失去作用。 - Bart van Heukelom
实际上,您应该将锁调用放在构造函数中,在finally处理之外。 - jtahlborn
8
包装器没有正确完成,这将会引起问题。只需问自己:按照旧的方式,为什么lock()语句要放在try块之外。 - Adrian Shum

7
< p >如果资源在离开try块时被创建和销毁,则try-with-resource可以很好地工作。但它对需要保持活动的资源无法起作用。锁不是在每次使用时创建和销毁的。它们被保持活动,只是被锁定和解除锁定。这就是为什么它们不是AutoClosable的原因。

正如其他人已经建议的那样,可以使用包装器来由try-with-resource块创建和销毁并执行锁定和解锁操作。


3

除非您忽略分配成本(大多数应用程序员可以做到,但锁库编写者无法做到),否则没有完美的解决方案。然后您可以使用包装器。

@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问题


2

我认为一个简单的工具方法,它接受一个锁和一个Runnable比使用try-with-resource语句与锁更好。

就像这样:

public static void locked(Lock lock, Runnable r) {
    lock.lock();

    try {
        r.run();
    } finally {
        lock.unlock();
    }
}

使用示例:

locked(lock, () -> {
    // Do your stuff
});

优点:

  • 使用try-with-resource不会创建虚拟变量。
  • 我认为这很清楚易懂。

缺点:

  • 对于每个调用,都会分配一个Runnable实例,其他一些解决方案避免了这种情况。但在几乎所有情况下,这都是微不足道的。
  • 只有当您可以使用Java 8时才能使用该方法。

1
考虑到用户2357112的精明建议
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可能会很有趣。


1
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();
    }
}

这个实现的一个明显问题是,因为你没有在构造函数中强制锁定,所以你不知道是否需要在 close() 函数中释放锁,这会给用户带来更多的工作,他们必须始终使用新的 AutoCloseableLockWrapper 创建 try-with-resources 块,然后在任何可能失败或返回之前进行 lock。同样,tryLock 函数也很“复杂”,因为你可能无法获取锁,但在退出 try-with-resources 块时它将被解锁... - David Rodríguez - dribeas
你怎样在 try-with-resources 块中使用这个实现?你需要一个执行锁定并返回 Closeable 对象的方法,它可以在 "try()" 中被调用。我有遗漏吗? - JohnC
@JohnC,只需使用AutoCloseableLockWrapper而不是lock。 - gstackoverflow
抱歉,如果我在这里表现得愚钝...我想要一个符合try-with-resource的锁定功能,以减少样板代码的混乱。我可以使用AutoCloseableLockWrapper来实现吗?如果可以,怎么做?你能提供示例用法吗? - JohnC

1

在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
}

1

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接