我突然意识到有人可能想出一个优雅的方法来做到这一点,基本上:
假设我有一个类:
public class SomeUtilClass {
public int someComplicatedTask(int x, int y, int z) {
// some very very complicated IO CPU bound task
}
// other equally lifting methods go here
}
是否有一种优雅的方法可以将线程池和任务队列放在每个方法的前面。
基本上我做的是这样的事情:
wrappedObject = EncapsulateWithThreadPoolAndTaskQueueFactory.wrap(SomeUtilClass,
10 /*worker threads*/);
String ticket1=wrappedObject.asyncExecSomeComplicatedTask(1,2,3);
String ticket2=wrappedObject.asyncExecSomeComplicatedTask(4,5,6);
String ticket2=wrappedObject.asyncExecSomeComplicatedTask(7,8,9);
wrappedObject.joinOnTicket(ticket1);
int result1=wrappedObject.getRestTicket(ticket1);
依此类推......
答案 0 :(得分:2)
我们之前使用过TaskFactory
系统回调。也就是说,我们将它用于请求和响应,但它可以用于许多事情。基本上,您将拥有TaskFactory
界面:
public interface TaskFactory<T, R> {
Callable<R> taskFor(T t);
}
public interface Callback<R> {
void onCallback(R r);
void onException(Throwable t);
}
对于请求/响应内容,我们使用TaskFactory<Request, Response>
的实例为Callable
对象提供Request
个实例,这些对象执行网络I / O并返回Response
:
public class RequestTaskFactory implements TaskFactory<Request, Response> {
public Callable<Response> taskFor(Request request) {
return new RequestTask(request);
}
}
任务:
class RequestTask implements Callable<Response> {
private final Request request;
public RequestTask(Request request) {
this.request = request;
}
public Response call() {
// Open socket
// Send request
// Receive response
Response resp = getResponse(socket);
return resp;
}
}
最后一个接口和类将它们组合在一起:
public interface TaskHandler<T, R> {
TaskFactory<T, R> getTaskFactory();
Future<R> submit(T t);
void submit(T t, Callback<R> callback);
}
如果您愿意,还可以使用另一个方法在事件派发线程上使用SwingUtilities.invokeLater
执行回调,以防您完全使用Swing。实施:
public class TaskHandlerImpl<T, R> implements TaskHandler<T, R> {
private final ExecutorService executor;
private final TaskFactory<T, R> taskFactory;
public TaskHandlerImpl(TaskFactory<T, R> taskFactory, int threadPoolSize) {
this.taskFactory = taskFactory;
this.executor = Executors.newFixedThreadPool(threadPoolSize);
}
public TaskFactory<T, R> getTaskFactory() {
return taskFactory;
}
public Future<R> submit(T t) {
return executor.submit(taskFactory.taskFor(t));
}
public void submit(T t, Callback<R> callback) {
executor.execute(new CallbackTask(taskFactory.taskFor(t)
}
private static class CallbackTask<R> implements Runnable {
private final Callback<R> callback;
private final Callable<R> callable;
CallbackTask(Callback<R> callback, Callable<R> callable) {
this.callback = callback;
this.callable = callable;
}
public void run() {
try {
callback.onCallback(callable.call());
} catch (Exception ex) {
callback.onException(ex);
}
}
}
}
然后,这只是编写TaskFactory
,Callable
和Callback
实现的问题。
答案 1 :(得分:2)
除了我更通用的并发答案之外的另一个选择是使用Java的Proxy
类。
假设您有一个类Foo
,并且您希望将其包装起来,以便它将在后台线程中执行所有方法。它有一个方法doSomething
需要很长时间才能执行。
public class Foo {
public void doSomething() {
// network I/O or something
Thread.sleep(5000);
}
}
要使用Proxy
,您需要一个界面。 (请参阅this tutorial。)从Foo
创建一个界面:
public interface Foo {
void doSomething();
}
让您的旧Foo
课程实现您的新界面:
public class FooImpl implements Foo { ... }
Java的Proxy
需要做两件事:接口(得到那个)和InvocationHandler
。 InvocationHandler
将执行后台线程中的方法。
以下是我们如何使用Proxy
的实例创建FooImpl
:
Foo foo = new FooImpl();
foo = (Foo) Proxy.newInstance(
System.getClassLoader(),
new Class[] { Foo.class },
new BackgroundInvocationHandler(foo));
非常简单,真的。现在我们只需要写BackgroundInvocationHandler
:
public class BackgroundInvocationHandler implements InvocationHandler {
private static final int THREAD_COUNT = 20;
private static final Executor executor = Executors.newFixedThreadPool(THREAD_COUNT);
private final Object obj;
public BackgroundInvocationHandler(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
executor.execute(new MethodInvoker(m, args, obj));
return null;
}
private static class MethodInvoker implements Runnable {
private final Method m;
private final Object[] args;
private final Object target;
MethodInvoker(Method m, Object[] args, Object target) {
this.m = m;
this.args = args;
this.target = target;
}
@Override
public void run() {
try {
m.invoke(target, args);
} catch(Exception e) {
// TODO log me
}
}
}
}
正如我相信你可能期望的那样,这只适用于返回void
的方法。对于返回不是Proxy
的内容的任何内容,null
实例都会返回void
。但是,您始终可以将此与回调或事件分派相结合,以从这些内容中获取结果。请记住,这就像提交作业并离开它们一样,因此如果不实现某种回调系统,您将无法获得任何回报。
还有一句话:如果你想确保所有你的对象被包装,那么你需要一个工厂来创建Foo
个对象:
public class FooFactory {
public static Foo createFoo() {
Foo foo = new FooImpl();
foo = (Foo) Proxy.newInstance(
System.getClassLoader(),
new Class[] { Foo.class },
new BackgroundInvocationHandler(foo));
return foo;
}
}
然后将FooImpl
包私有,这样包外的任何人都不能通过从类声明中删除public
来访问它:
class FooImpl implements Foo { ... }
你去吧。这是使用简单方法调用将对象包装为后台调用的好方法。此代码将在后台执行doSomething
并立即打印给定语句:
Foo myFoo = FooFactory.createFoo();
myFoo.doSomething();
System.out.println("Prints immediately without waiting");
与我的其他答案相比,这更接近你想要的东西,但由于返回类型的要求,它更受限制。
答案 2 :(得分:1)
而不是各种(可能是多个)客户端类对代码进行小包装,我更喜欢实现方法的Runnable或Callable版本,以实现它们,以鼓励用户(通过制作它)容易)异步使用它们。显然,这意味着您预计它们将被异步使用。你甚至可以传递参数。 e.g。
public class Foo {
public String someLongMethodA(Object...args) {
// code that takes a long time here
return someResult;
}
public Callable<String> callableLongMethodA(final Object...args) {
return new Callable<String>() {
@Override
public String call() throws Exception {
return someLongMethodA(args);
}
};
}
public Integer someLongMethod2(int loopCount) {
// yet more long running code here
return 42; // the meaning of life
}
public Callable<Integer> callableLongMethod2(final int loopCount) {
return new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return someLongMethod2(loopCount);
}
};
}
}
这远非完美,因为您正在编写大量的样板代码。但这就是Eclipse模板的用途。此外,在某些情况下,您必须小心,客户在进行初始调用后不会更改参数。