管理不对称资源使用的最佳设计模式

时间:2017-10-21 15:25:59

标签: java design-patterns try-with-resources

我想就使用托管资源的最佳设计模式绘制一些意见,其中涉及两个不同的资源,但您需要以与获取它们相反的顺序发布它们。

首先,让我设置场景。我们正在使用两种类型的对象文档和文档集合。 “文档集合”字面上包含对文档的引用和每个文档的一些元数据。

最初我们有一个对称的模式,流动如下:

  1. 锁定集合
  2. 使用Collection
  3. 做有用的东西
  4. 锁定文档
  5. 使用Collection and Document
  6. 做有用的事情
  7. 解锁文件
  8. 解锁收藏
  9. 并在代码中表示如下:

    Collection col = null;
    try {
        col = getCollection("col1 name", LockMode.WRITE_LOCK);
    
        // Here we do any operations that only require the Collection
    
        Document doc = null;
        try {
            doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK);
    
            // Here we do some operations on the document (of the Collection)
    
        } finally {
            if (doc != null) {
                doc.close();
            }
        }
    
    } finally {
        if (col != null) {
            col.close();
        }
    }
    

    既然Java 7之后我们已经try-with-resources了,我们已对其进行了改进,以便Java代码描述自动释放资源:

    try (final Collection col = getCollection("col1 name", LockMode.WRITE_LOCK)) {
    
        // Here we do any operations that only require the Collection
    
        try (final Document doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK)) {
    
            // Here we do some operations on the document (of the Collection)
    
        }
    
    }
    

    我们遇到的问题是,在我们对文档执行操作时保持Collection锁定是低效的,因为其他线程必须等待,并且文档上的操作通常不需要修改Collection。

    因此我们希望采用不对称模式,以便我们尽快发布收藏品。流程应该是:

    1. 锁定集合
    2. 使用Collection
    3. 做有用的东西
    4. 锁定文档
    5. 做任何需要收藏和文件(罕见)
    6. 的事情
    7. 解锁收藏
    8. 使用Document
    9. 执行有用的操作
    10. 解锁文件
    11. 我想知道在代码中实现这种不对称方法的最佳模式。这显然可以通过try / finally等完成,如下所示:

      Collection col = null;
      Document doc = null;
      try {
          col = getCollection("col1 name", LockMode.WRITE_LOCK);
      
          // Here we do any operations that only require the Collection
          try {
              doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK);
      
              // Here we do any operations that require both the Collection and Document (rare).
      
          } finally {
              if (col != null) {
              col.close();
          }
      
          // Here we do some operations on the document (of the Collection)
      
      } finally {
          if (doc != null) {
                  doc.close();
              }
          }
      }
      

      我还可以想到一个try-with-resources方案,我们交换资源发布顺序,但我想知道是否这使得阅读代码不太容易理解。例如:

      try (final ManagedRelease<Collection> mcol =
              new ManagedRelease<>(getCollection("col1 name", LockMode.WRITE_LOCK))) {
      
          // Here we do any operations that only require the Collection
      
          try (final ManagedRelease<Document> mdoc =
                  mcol.withAsymetrical(mcol.resource.getDocument("doc1 name", LockMode.WRITE_LOCK))) {
      
              // Here we do any operations that require both the Collection and Document (rare).
      
          }  // NOTE: Collection is released here
      
          // Here we do some operations on the document (of the Collection)
      
      }  // NOTE: Document is released here
      

      ManagedRelease类:

      private static class ManagedRelease<T extends AutoCloseable> implements AutoCloseable {
          final T resource;
          private Supplier<Optional<Exception>> closer;
      
          public ManagedRelease(final T resource) {
              this.resource = resource;
              this.closer = asCloserFn(resource);
          }
      
          private ManagedRelease(final T resource, final Supplier<Optional<Exception>> closer) {
              this.resource = resource;
              this.closer = closer;
          }
      
          public <U extends AutoCloseable> ManagedRelease<U> withAsymetrical(final U otherResource) {
              // switch the closers of ManagedRelease<T> and ManagedRelease<U>
              final ManagedRelease<U> asymManagedResource = new ManagedRelease<>(otherResource, closer);
              this.closer = asCloserFn(otherResource);
              return asymManagedResource;
          }
      
          @Override
          public void close() throws Exception {
              final Optional<Exception> maybeEx = closer.get();
              if(maybeEx.isPresent()) {
                  throw maybeEx.get();
              }
          }
      
          private static Supplier<Optional<Exception>> asCloserFn(final AutoCloseable autoCloseable) {
              return () -> {
                  try {
                      autoCloseable.close();
                      return Optional.empty();
                  } catch (final Exception e) {
                      return Optional.of(e);
                  }
              };
          }
      }
      

      我欢迎就try-with-resources非对称资源管理的方法是否合理,以及对其他可能更合适的模式的指示表示欢迎。

3 个答案:

答案 0 :(得分:3)

第一个问题似乎是未明确的预期行为。特别是,如果Collection.close抛出Exception,会发生什么?是否应继续进行Document处理?是否应该回滚在两个锁下完成的部分文档处理?

如果答案是Collection.close从未实际抛出任何异常(或者你不关心会发生什么),恕我直言最简单的解决方案是让你的Collection.close幂等,然后明确地调用它在适当的try-with-resources区块的中间。如果在关闭的Collection上调用,那么使“通常”IllegalStateException方法提升Collection之类的内容也是很有意义的。然后你的第二个例子会变成这样:

try (final Collection col = getCollection("col1 name", LockMode.WRITE_LOCK)) {
    // Here we do any operations that only require the Collection

    try (final Document doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK)) {

        // Here we do any operations that require both the Collection and Document (rare).


        // NOTE: usually Collection is released here
        col.close();
        // optionally make `col` not final and explicitly set it to `null`
        // here so IDE would notify you about any usage after this point

        // Here we do some operations on the document (of the Collection)

    }  
}  

如果您无法更改Collection.close代码,则可以将ReleaseManager更改为close幂等。您也可以选择将其重命名为ResourceManager。在那里添加一个getter,并且始终只通过getter访问资源。如果在IllegalStateException之后调用,则getter将抛出close

如果Collection.close实际上可能会抛出一些异常并且您确实关心这种情况,那么很难在不知道预期行为的情况下提供解决方案。

答案 1 :(得分:2)

我会给你一个像这样的通用,完整和链接的解决方案:

   public static void sample() {
    Resource resourceA = new Resource("A");
    Resource resourceB = new Resource("B");
    LockVisitor.create(resourceA)
        .lock()// lock A
        .doOnValue(Main::doSomething)// do for A
        .with(resourceB)// join with B
        .lock()// lock A & B (A has been locked)
        .doOnBoth(Main::doSomething)// do for A and B
        .toRight()// only need B (unlock A)
        .doOnValue(Main::doSomething)// do for B
        .close();// unlock B
  }

  private static void doSomething(Resource... rs) {
    System.out.println("do with: " + Arrays.toString(rs));
  }

sample将输出您的预期:

lock: Resource(A)
do with: [Resource(A)]
lock: Resource(B)
do with: [Resource(A), Resource(B)]
unlock: Resource(A)
do with: [Resource(B)]
unlock: Resource(B)

首先,我们应该定义可锁定资源。如何锁定以及如何解锁。

public interface Lockable extends AutoCloseable {

  void lock() throws Exception;

  void unlock() throws Exception;

  boolean isLocked();

  @Override
  default void close() throws Exception {
    unlock();
  }
}

您可以让您的班级实现此界面以获得更明确的通话。

然后我们可以构建我们的LockVisitor(为了减少这个答案的长度,我删除方法实现。You can find the complete code on github.

import io.reactivex.functions.Consumer;

public class LockVisitor<T extends Lockable> implements AutoCloseable {
  public static <T extends Lockable> LockVisitor<T> create(T lockable) {
    return new LockVisitor<>(lockable);
  }

  T value;
  Exception error;

  public LockVisitor(T value);

  public LockVisitor<T> lock();

  public LockVisitor<T> unlock();

  public LockVisitor<T> doOnValue(Consumer<T> func);

  public LockVisitor<T> doOnError(Consumer<Exception> func);

  public <B extends Lockable> TwoLockVisitor<T, B> with(LockVisitor<B> other);

  public <B extends Lockable> TwoLockVisitor<T, B> with(B other);
}

和我们TwoLockVisitor一起访问两个资源:

import io.reactivex.functions.BiConsumer;
import io.reactivex.functions.Consumer;

public class TwoLockVisitor<A extends Lockable, B extends Lockable> {
  public static <A extends Lockable, B extends Lockable> TwoLockVisitor<A, B> create(A a, B b) {
    return new TwoLockVisitor<>(LockVisitor.create(a), LockVisitor.create(b));
  }

  LockVisitor<A> left;
  LockVisitor<B> right;

  public TwoLockVisitor(LockVisitor<A> left, LockVisitor<B> right);

  public TwoLockVisitor<A, B> lock();

  public TwoLockVisitor<A, B> unlock();

  public TwoLockVisitor<A, B> doOnLeft(Consumer<A> func);

  public TwoLockVisitor<A, B> doOnRight(Consumer<B> func);

  public TwoLockVisitor<A, B> doOnBoth(BiConsumer<A, B> func);

  public LockVisitor<A> toLeft();

  public LockVisitor<B> toRight();
}

现在,您可以使用这些类来管理任何订单的资源。

答案 2 :(得分:1)

您的ManagedRelease计划肯定会使代码不易理解。使用语言功能最明确地表达您的意图是这样的:

try (final Collection col = getCollection("col1 name", LockMode.WRITE_LOCK)) {

    // Here we do any operations that only require the Collection

}
try (final Collection col = getCollection("col1 name", LockMode.WRITE_LOCK;
    final Document doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK)) {

    // Here we do any operations that require both the Collection and Document (rare).

}
try (final Document doc = col.getDocument("doc1 name", LockMode.WRITE_LOCK)) {

    // Here we do some operations on the document (of the Collection)

}

这个问题是每个锁的额外释放和重新获取,以及col超出了最后一次getDocument调用的范围,所以它不会很好地编译为是

我建议通过对ManagedRelease概念的不同看法来解决这个问题,提升一个级别。我为此设想的使用模式可以这样工作:

// The lambdas here are Supplier
try (final ReleaseManager<Collection> colManager = new ReleaseManager<>(() -> getCollection("col1 name", LockMode.WRITE_LOCK);
    final ReleaseManager<Document> docManager = new ReleaseManager<>(() -> colManager.getResource().get().getDocument("doc1 name", LockMode.WRITE_LOCK)) {

    try (final Managed<Collection> colManaged = colManager.getResource()) {

        // Here we do any operations that only require the Collection

    } // Here the resource close does nothing

    try (final Managed<Collection> colManaged = colManager.getResourceForLastUse();
        final Managed<Document> docManaged = docManager.getResource()) {

        // Here we do any operations that require both the Collection and Document (rare).

    } // Here the close of colManaged actually closes it, while docManaged.close() is a no-op

    try (final Managed<Document> docManaged = docManager.getResourceForLastUse()) {

        // Here we do some operations on the document (of the Collection)

    } // Here the document gets closed
} // Here the managers get closed, which would close their resources if needed

这与每个块中使用的资源相同,使用try-with-resources语言功能,在上次使用后立即释放每个资源,并且只获取每个锁一次。

有关ReleaseManager的说明:

ReleaseManager这是一个通用类,它为资源采用Supplier,在第一次getResource()调用时懒惰地调用它,并为将来的调用记住结果。 getResource()返回一个在关闭时不执行任何操作的包装器,getResourceForLastUse()返回一个包装器,它在包装器关闭时实际关闭资源;我把它们写成同一个班级,但你可以把它们变成不同的班级,我不确定它是否真的能让它更清晰。

ReleaseManager本身也实现了AutoCloseable,其close()实现是一个故障保护,如果资源已经获取但未关闭,它将关闭资源。我会考虑让它以某种方式记录警告,以便在资源的最后一次使用未被正确声明为最后一次时引起注意。最后一个考虑因素是,如果资源已经关闭,两种资源检索方法都应该抛出。

如果你喜欢这个解决方案,我将ReleaseManager的实施作为练习。