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 -
I
. A
, B
, C
and D
implements I
. valueOf
and object overriding
to remove case statement. I
from all the methods. 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.
You need to approach the result in multiple steps:
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)
.
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.
Map
/ MultiMap
as defined above, eg by unsing multiple calls to your dispatch
method. 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.