我正在尝试用Java实现一个工作队列,它限制了一次可以采取的工作量。特别是,它试图保护对外部资源的访问。我目前的方法是使用Semaphore和BlockingQueue,这样我就有这样的东西:
interface LimitingQueue<V> {
void put(Callable<V> work);
Callable<V> tryPoll();
}
它的行为应该是这样的:
@Test
public void workLimit() throws Exception {
final int workQueue = 2;
final LimitingQueue<Void> queue = new LimitingQueue<Void>(workQueue);
queue.put(new Work()); // Work is a Callable<Void> that just returns null.
queue.put(new Work());
// Verify that if we take out one piece of work, we don't get additional work.
Callable<Void> work = queue.tryPoll();
assertNotNull(work, "Queue should return work if none outstanding");
assertNull(queue.tryPoll(), "Queue should not return work if some outstanding");
// But we do after we complete the work.
work.call();
assertNotNull(queue.tryPoll(), "Queue should return work after outstanding work completed");
}
tryPoll()
的实施使用Semaphore#tryAcquire
,如果成功,会创建一个匿名的Callable,它围绕{{1}的调用将Semaphore#release
调用包装在try/finally
块中}}
这有效,但有些不满意的是,如果此类的用户放置了一些实现Callable的特定类的工作,则在查看{{1}的结果时,用户无法访问该类。 }。值得注意的是,work.call()
会返回tryPoll
,而不是tryPoll()
。
是否有办法实现工作限制效果,同时为调用者提供对提交的工作对象的可用引用? (可以将Callable<Void>
的类型签名加强为更像Work
。)我想不出一种方法可以确保在调用工作项后不释放信号量而不执行此类工作包装
答案 0 :(得分:3)
EDIT2我已经取代了这里提出的关于如何实现所需内容的建议。如果你想要一些旧的信息,请告诉我,我可以恢复它。
public class MyQueue<T> {
private Semaphore semaphore;
public void put(Work<T> w) {
w.setQueue(this);
}
public Work<T> tryPoll() {
return null;
}
public abstract static class Work<T> implements Callable<T> {
private MyQueue<T> queue;
private void setQueue(MyQueue<T> queue) {
if(queue != null) {
throw new IllegalStateException("Cannot add a Work object to multiple Queues!");
}
this.queue = queue;
}
@Override
public final T call() throws Exception {
try {
return callImpl();
} finally {
queue.semaphore.release();
}
}
protected abstract T callImpl() throws Exception;
}
}
然后像这样使用它:
public class Test {
public static void main(String[] args) {
MyQueue<Integer> queue = new MyQueue<Integer>();
MyQueue.Work<Integer> work = new MyQueue.Work<Integer>() {
@Override
protected Integer callImpl() {
return 5;
}
};
queue.put(work);
MyQueue.Work<Integer> sameWork = queue.tryPoll();
}
}
答案 1 :(得分:0)
听起来我应该只使用内置ExecutorService。使用Executors#newCachedThreadPool获取池,然后submit
Callable
返回Future
的作业。