简体   繁体   中英

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. 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. 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.

I would do it this way -

  • Create an interface, let's say I .
  • Have classes A , B , C and D implements I .
  • Use enums valueOf and object overriding to remove case statement.
  • Use polymorphism and return I from all the methods.
  • Below is the code (not including A , B , C , D , I ) as they are plain class and interface - not doing much.

Below is the code:

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

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

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

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.

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. So you could just create a method 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. Because of Java's type erasure this handle has to store the Class<T> somehow. So the most simple handle would be the according Future<T> itself (with T being one of A and B in your example).

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

You might replace your enum MethodNames with this handle.

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.
  2. Use waitAll with the list of the values of the map
  3. Fetch the respective results from the Map / MultiMap with the get method described above

You are trying to do something like fork-join or map-reduce kind of stuff. 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. wait on the Result in current thread. Run another thread inside Result which will monitor futures and notify when all methods are returned.

When Result notifies, you move ahead in the current thread with all returned data held by the Result object.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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