我想就使用托管资源的最佳设计模式绘制一些意见,其中涉及两个不同的资源,但您需要以与获取它们相反的顺序发布它们。
首先,让我设置场景。我们正在使用两种类型的对象文档和文档集合。 “文档集合”字面上包含对文档的引用和每个文档的一些元数据。
最初我们有一个对称的模式,流动如下:
并在代码中表示如下:
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。
因此我们希望采用不对称模式,以便我们尽快发布收藏品。流程应该是:
我想知道在代码中实现这种不对称方法的最佳模式。这显然可以通过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
非对称资源管理的方法是否合理,以及对其他可能更合适的模式的指示表示欢迎。
答案 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
的实施作为练习。