1
  1. 我想不出一个更好的标题,所以很感激编辑。
  2. 我遇到的问题是,库公开的许多方法需要花费大量时间,因为它们会做很多脑力劳动或阻塞 I/O ......例如,当我要求 Solr 索引文档时。
  3. 因此,为了解决这个问题,我最终要做的是编写包装器代码,该代码本质上是调用线程的 run() 方法中的方法。我启动工作线程以实现并行性,然后提取结果并将它们与原始调用进行比较。
  4. 我突然想到有人可能想出了一个优雅的方法来做到这一点,基本上:

    1. 采取一种方法
    2. 获取某种工厂方法以将其转换为异步调用
    3. 此外,围绕它包装一个 ExecutorService,这样您就可以将调用排队并让一个工作线程池通过它。

假设我有一个像这样的课程:

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);

等等……

4

3 回答 3

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>来为我们提供执行网络 I/O 并返回的对象的Callable实例:RequestResponse

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

然后,这几乎只是编写您的TaskFactoryCallableCallback实现的问题。

于 2012-09-13T22:40:34.580 回答
2

除了我更一般的并发答案之外,另一个选择是使用 Java 的Proxy类。

假设您有一个类Foo,并且想要包装它,以便它将在后台线程中执行其所有方法。它有一个doSomething需要很长时间才能执行的方法。

public class Foo {

    public void doSomething() {
        // network I/O or something
        Thread.sleep(5000);
    }
}

要使用Proxy,您需要一个接口。(请参阅本教程。)从以下位置创建接口Foo

public interface Foo {
    void doSomething();
}

并让你的旧Foo类实现你的新接口:

public class FooImpl implements Foo { ... }

JavaProxy需要两件事才能工作:接口(得到了)和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");

与我的其他答案相比,这更接近您想要的,但由于返回类型要求而受到更多限制。

于 2012-09-14T02:38:10.767 回答
1

我更喜欢在实现它们的地方实现方法的 Runnable 或 Callable 版本,而不是各种(并且可能是多个)客户端类对代码进行小包装,以鼓励用户(通过简化)异步使用它们。显然,这意味着您预计它们将被异步使用。你甚至可以传递参数。例如

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 模板的用途。此外,在某些情况下,您必须小心客户端在进行初始调用后不会更改参数。

于 2012-09-13T23:17:15.980 回答