简体   繁体   English

具有不同返回类型的可变callables

[英]Variable callables with different return types

The problem I have in hand is that I methods one(), two(), three(), four() that have a different return types say, A, B, C, D and I need to spawn variable numbers of threads (one for each method depending on the use case. This means I would want to call a subset of methods at a time.) Now, am using the cachedThreadPool to submit these callables. 我手头的问题是我的方法one(), two(), three(), four()有不同的返回类型,比如A, B, C, D和我需要产生可变数量的线程(每个方法一个,具体取决于用例。这意味着我希望一次调用一个方法的子集。)现在,我使用cachedThreadPool来提交这些callables。 Some code below: 下面的一些代码:

public class Dispatcher {

  public void dispatch(List<MethodNames> methodNames) {
    //Now I am going to iterate through the list of methodNames
    //And submit each method to the `ExecutorService`
    for(MethodNames m : methodNames) {
      switch(m) {
        case ONE: //submit one()
                  //wait and get the future
                  Future<A> future = cachePool.submit(new Callable<A>() {
                    @Override
                    public A call() {
                      return one();
                    });
                  A response = future.get(); 
             break;
        ....
      }
    }
  }
}

public enum MethodNames {
  ONE, TWO, THREE, FOUR
}

//Example methods:
public A one() {
}

public B two() {
}

My question is how do the above such that all the method calls are made without having to wait for one to finish. 我的问题是上面如何做所有的方法调用,而不必等待一个完成。 Also, how do I gather all the futures and wait for them to finish cause all the futures have a different generic type Future<A>, Future<B> etc. I make the call to submit() inside the case statement so I don't have the access to the returned Future<T> outside the case. 另外,我如何收集所有futures并等待它们完成因为所有期货都有不同的泛型类型Future<A>, Future<B>等我在case语句中调用submit()所以我不要在案例之外无权访问返回的Future<T> Now I could do an if, else instead of the for loop but I am trying to figure out if there is a better way to achieve this. 现在我可以做一个if, else而不是for循环,但我想弄清楚是否有更好的方法来实现这一点。

I would do it this way - 我会这样做 -

  • Create an interface, let's say I . 创建一个界面,让我们说I
  • Have classes A , B , C and D implements I . ABCD实现I
  • Use enums valueOf and object overriding to remove case statement. 使用枚举valueOf和对象overriding来删除case语句。
  • Use polymorphism and return I from all the methods. 使用多态并从所有方法返回I
  • Below is the code (not including A , B , C , D , I ) as they are plain class and interface - not doing much. 下面是代码(不包括ABCDI ),因为它们是普通类和接口 - 没有做太多。

Below is the code: 以下是代码:

Dispatcher.java Dispatcher.java

package com.test.thread;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

public class Dispatcher {

public void dispatch() throws InterruptedException, ExecutionException {
    Map<MethodNames, Future<I>> reponse = new HashMap<MethodNames, Future<I>>();
    ExecutorService cachePool = Executors.newCachedThreadPool();
    for (MethodNames methodNames : MethodNames.values()) {
        Future<I> future = cachePool.submit(methodNames.worker());
        reponse.put(methodNames, future);
    }
    cachePool.awaitTermination(5, TimeUnit.MINUTES);
    for(MethodNames key : reponse.keySet()) {
        I result = reponse.get(key).get();
        System.out.println("result :: " + result);
    }
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
    new Dispatcher().dispatch();
}

} }

MethodNames.java MethodNames.java

package com.test.thread;

import java.util.concurrent.*;

public enum MethodNames {
ONE {
    @Override
    public Callable<I> worker() {
        return new Callable<I>() {
            @Override
            public I call() throws InterruptedException {
                System.out.println("Thread1");
                TimeUnit.SECONDS.sleep(30);
              return new A();
            }};
    }
},
TWO {
    @Override
    public Callable<I> worker() throws InterruptedException {
        return new Callable<I>() {
            @Override
            public I call() throws InterruptedException {
                System.out.println("Thread2");
                TimeUnit.SECONDS.sleep(30);
              return new B();
            }};
    }
},
THREE {
    @Override
    public Callable<I> worker() throws InterruptedException {
        return new Callable<I>() {
            @Override
            public I call() throws InterruptedException {
                System.out.println("Thread3");
                TimeUnit.SECONDS.sleep(30);
              return new C();
            }};
    }
},
FOUR {
    @Override
    public Callable<I> worker() throws InterruptedException {
        return new Callable<I>() {
            @Override
            public I call() throws InterruptedException {
                System.out.println("Thread");
                TimeUnit.SECONDS.sleep(30);
              return new D();
            }};
    }
};
public abstract Callable<I> worker() throws InterruptedException;

} }

It seems best to split the get from the future, hence add a Callable as parameter to the enum. 最好将get与未来分开,因此将一个Callable作为参数添加到枚举中。 Then the enum instant can create a Future. 然后,枚举瞬间可以创造一个未来。 For generic typing unfortunately the resulting Class needs to be stored, and used to have a correct typing. 不幸的是,对于通用类型,需要存储生成的类,并用于正确键入。

public enum MethodNames {
    ONE(A.class, () -> { one() }),
    TWO(B.class, () -> { two() }),
    ...
    FOUR(D.class, () -> { four() });

    private final Class<?> resultType;
    private final Future<?> future;
    private <T> MethodNames(Class<T> resultType, Callable<T> callable) {
        this.resultType = resultType;
        future = cachePool.submit(callable);
    }

    public <T> T getResponse(Class<T> type) {
        Object response = future.get();
        return resultType.asSubclass(type).cast(response);
    }
}

If these are the only Callables that you're submitting to the ExecutorService then you can call awaitTermination on cachePool after submitting your jobs (which can be Runnable instead of Callable ) 如果这些是您提交给ExecutorService的唯一Callables ,那么您可以在提交作业后调用cachePool上的cachePool (可以是Runnable而不是Callable

public class Dispatcher {
  public void dispatch(List<MethodNames> methodNames) {
    for(MethodNames m : methodNames) {
      switch(m) {
        case ONE: //submit one()
                  cachePool.execute(new Runnable() {
                    @Override
                    public void run() {
                      // do work
                    });
             break;
        ....
      }
    }
  }
  cachePool.awaitTermination(100, TimeUnit.HOURS);
}

If there are other unrelated tasks in cachePool or for some other reason you can't use awaitTermination then you can block on a Semaphore instead. 如果cachePool有其他不相关的任务或由于某些其他原因你不能使用awaitTermination那么你可以阻塞信号量 Initialize the Semaphore with zero permits, each task will release a permit when it completes, and the dispatch method blocks on semaphore.acquire(methodNames.size()) which waits until all tasks have called release (and hence completed). 使用零许可初始化Semaphore ,每个任务在完成时release许可,并且dispatch方法在semaphore.acquire(methodNames.size())semaphore.acquire(methodNames.size()) ,等待所有任务调用release (并因此完成)。 Note the try-finally block in the Runnable , otherwise if the Runnable throws an exception then it won't call release and the dispatch method will block forever. 注意Runnabletry-finally块,否则如果Runnable抛出异常,那么它将不会调用release并且dispatch方法将永远阻塞。

public class Dispatcher {
  public void dispatch(List<MethodNames> methodNames) {
    Semaphore semaphore = new Semaphore(0);
    for(MethodNames m : methodNames) {
      switch(m) {
        case ONE: //submit one()
                  cachePool.execute(new Runnable() {
                    @Override
                    public void run() {
                      try {
                        // do work
                      } finally {
                        semaphore.release();
                      }
                    });
             break;
        ....
      }
    }
  }
  semaphore.acquire(methodNames.size());
}

If you're collecting the results of the tasks (it doesn't look like you're doing this at the moment, but requirements tend to change) then each Runnable can store its result in a shared ConcurrentLinkedQueue or some other thread-safe data structure (or one data structure per return type etc), and then dispatch can process these results when the semaphore.acquire or awaitTermination method unblocks. 如果你正在收集任务的结果(看起来你现在看起来不像这样,但需求往往会改变),那么每个Runnable都可以将其结果存储在共享的ConcurrentLinkedQueue或其他一些线程安全的数据中结构(或每个返回类型的一个数据结构等),然后当semaphore.acquireawaitTermination方法解除awaitTermination时, dispatch可以处理这些结果。

The ingredients 这些成分

You need to approach the result in multiple steps: 您需要以多个步骤处理结果:

Waiting for multiple futures 等待多个期货

In this case you can just use Future<?> in the signature, because just for waiting you don't have to know about the result type. 在这种情况下,您可以在签名中使用Future<?> ,因为只是等待您不必了解结果类型。 So you could just create a method void waitForAll(List< Future<?> > futures) . 所以你可以创建一个方法void waitForAll(List< Future<?> > futures)

Getting the result from an unknown future in a safe way 以安全的方式从未知的未来获得结果

For this you need some kind of handle that knows about the type the Future will provide. 为此你需要一些知道Future将提供的类型的句柄。 Because of Java's type erasure this handle has to store the Class<T> somehow. 由于Java的类型擦除,这个句柄必须以某种方式存储Class<T> So the most simple handle would be the according Future<T> itself (with T being one of A and B in your example). 所以最简单的句柄就是相应的Future<T>本身(在你的例子中TAB之一)。

So you could just store the futures in a Map<Class<?>, Future<?>) (or a MultiMap ) with an additional get method of the type Future<T> get<T>(Class<T> handle) . 因此,您可以将期货存储在Map<Class<?>, Future<?>) (或MultiMap )中,并使用Future<T> get<T>(Class<T> handle)类型的附加get方法。

You might replace your enum MethodNames with this handle. 您可以使用此句柄替换enum MethodNames

The receipt: Combining the ingredients to a solution 收据:将成分与解决方案相结合

  1. Create a Map / MultiMap as defined above, eg by unsing multiple calls to your dispatch method. 创建如上定义的Map / MultiMap ,例如通过取消对dispatch方法的多次调用。
  2. Use waitAll with the list of the values of the map 将waitAll与地图的值列表一起使用
  3. Fetch the respective results from the Map / MultiMap with the get method described above 使用上述get方法从Map / MultiMap获取相应的结果

You are trying to do something like fork-join or map-reduce kind of stuff. 你正在尝试做一些像fork-join或map-reduce这样的东西。 You may find an established mechanism to do this, instead of reinventing the wheel. 您可能会找到一种既定机制来完成此任务,而不是重新发明轮子。

Anyway getting back to your specific problem of waiting for all methods to finish and moving on: 无论如何回到你等待所有方法完成和继续前进的具体问题

As you mentioned you should not lose the pointer to future itself. 正如你所提到的,你不应该失去指向未来的指针。 So create a structure Result , which could hold all the futures. 因此,创建一个结构Result ,它可以容纳所有未来。 wait on the Result in current thread. 等待当前线程中的Result Run another thread inside Result which will monitor futures and notify when all methods are returned. Result中运行另一个线程,它将监视期货并在返回所有方法时通知

When Result notifies, you move ahead in the current thread with all returned data held by the Result object. Result通知时,您将在当前线程中向前移动,并返回Result对象保存的所有返回数据。

Simple (limited) solution : this is really useful if you can define an interface / super class for the return values (similar to the sql.ResultSet ), otherwise not so much... then the switch appears again when working with the results because you have to cast it. 简单(有限)解决方案 :如果您可以为返回值定义接口/超类(类似于sql.ResultSet ),这非常有用,否则没有那么多......然后在处理结果时再次出现切换因为你必须施展它。

Dispatch: 调度:

dispatcher.dispatch(new SuperABC[] {new A(), new B(), new C()});
// dispatcher.dispatch(new A(), new B(), new C()); with ... notation, see comment below

Interfaces: 接口:

public interface Result { ... }
public interface SuperABC extends Callable<Result> {}

Class example: 类示例:

public class A implements SuperABC {
    public Result call() throws Exception { ... }
}

dispatch method: 调度方式:

public Result[] dispatch(SuperABC[] tasks) { // dispatch(SuperABC... tasks)
    List<Future<Result>> refs = new ArrayList<Future<Result>>(tasks.length);
    Result[] ret = new Result[tasks.length];
    for (SuperABC task : tasks)
        refs.add(cachedThreadPool.submit(task));
    for (int i = 0; i < tasks.length; i++)
        ret[i] = refs.get(i).get();
    return ret;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM