【问题标题】:Are Locks AutoCloseable?锁可以自动关闭吗?
【发布时间】:2011-10-21 09:33:56
【问题描述】:

Locks 可以自动关闭吗?也就是说,而不是:

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

...我可以说:

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

...在 Java 7 中?

【问题讨论】:

  • 您可以提出要求让他们这样做

标签: java resources java-7 resource-management locks


【解决方案1】:

我自己正在考虑这样做并做了这样的事情:

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

【讨论】:

  • 这是我目前正在做的事情,它让开发变得轻而易举。对此 +1 还不够。
  • 因为覆盖是关闭的,并且打开/关闭比锁定/关闭更好(听起来他们做同样的事情)。
  • 为此,该方法需要返回对象,但 lock() 继承自 ReentrantLock 并且具有 void 返回类型。由于lock() 在不破坏继承的情况下不可用,因此open()close() 一起使用是有意义的。
  • 我会考虑使锁本身不能自动关闭,而是让open 返回一个单独的对象,其close 释放锁。否则,您可能会冒着人们在没有open 调用的情况下执行try(CloseableReentrantLock closeableLock = lock) 的风险。
  • 这是最好的解决方案,但考虑到它仍然容易出错,我可能会在由多个开发人员维护的大型代码库中坚持使用传统的(非 try-with-resource)方法。
【解决方案2】:

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

不过,我认为您最好还是坚持使用旧语法。让您的锁定逻辑完全可见会更安全。

【讨论】:

  • 只是为了向阅读本文的初学者澄清:在实际代码中,您不应该在每次锁定它时都创建一个新锁,因为这会删除它的功能。
  • 实际上,您可能希望将锁调用放在构造函数中,在最终处理之外
  • 包装器没有正确完成,会造成麻烦。问问自己:以旧的方式,为什么 lock() 语句放在 try 块之外。
【解决方案3】:

通用的ReentrantLock 既不实现也不提供实现try-with-resources 语句所需的AutoCloseable 接口的任何东西。不过这个概念对于 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
}

【讨论】:

  • 这似乎没有回答有关 Java 7 和 AutoCloseable 的问题。你的意思是这是一个单独的问题吗?
  • 您的 Java 8 解决方案非常优雅。做得好!我在stackoverflow.com/a/48145269/14731ReadWriteLock 上发布了一个相关设计
  • 最好的解决方案,应该上顶!
【解决方案4】:

try-with-resource 适用于在留下try-block 时创建和销毁的资源。它不适用于需要保持活力的资源。每次使用时都不会创建和销毁锁。它们保持活力,只是锁定和解锁。这就是为什么他们不是AutoClosable

正如其他人已经建议的那样,包装器可用于由 try-with-resource 块创建和销毁,并在创建和销毁时进行锁定和解锁。

【讨论】:

    【解决方案5】:

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

    @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
    

    另见我的question on CR

    【讨论】:

      【解决方案6】:

      我认为使用锁和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
      });
      

      优点:

      • 没有为 try-with-resource 创建虚拟变量。
      • 我觉得很清楚。

      缺点

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

      【讨论】:

        【解决方案7】:
        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()函数中释放锁,这会增加更多的工作量用户,他们必须始终使用新的 AutoCloseableLockWrapperlock 创建 try-with-resources 块,然后任何事情都可能失败或返回。同样,tryLock 函数很“复杂”,因为您可能不会抓住锁,但在退出 try-with-resources 块时,它将被解锁......
        • 如何在 try-with-resource 块中使用此实现?您需要一个执行锁定并返回可在“try()”内部调用的 Closeable 的方法。我错过了什么吗?
        • @JohnC,只需使用 AutoCloseableLockWrapper 而不是 lock
        • 对不起,如果我在这里很密集。我想要一个与 try-with-resource 相匹配的锁定功能,以减少样板混乱。我可以用 AutoCloseableLockWrapper 做到这一点吗?如果是这样,怎么做?能否提供使用示例?
        【解决方案8】:

        考虑user2357112's shrewd advice

        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 会很有趣。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              这是另一种效果很好且超级高效的解决方案,但代价是每个锁定请求都需要查找 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
              }
              

              希望此解决方案有助于通过重用资源释放处理程序来实现单锁和多锁策略。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2014-04-06
                • 2012-09-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-11-29
                • 1970-01-01
                相关资源
                最近更新 更多