簡體   English   中英

具有不同返回類型的可變callables

[英]Variable callables with different return types

我手頭的問題是我的方法one(), two(), three(), four()有不同的返回類型,比如A, B, C, D和我需要產生可變數量的線程(每個方法一個,具體取決於用例。這意味着我希望一次調用一個方法的子集。)現在,我使用cachedThreadPool來提交這些callables。 下面的一些代碼:

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() {
}

我的問題是上面如何做所有的方法調用,而不必等待一個完成。 另外,我如何收集所有futures並等待它們完成因為所有期貨都有不同的泛型類型Future<A>, Future<B>等我在case語句中調用submit()所以我不要在案例之外無權訪問返回的Future<T> 現在我可以做一個if, else而不是for循環,但我想弄清楚是否有更好的方法來實現這一點。

我會這樣做 -

  • 創建一個界面,讓我們說I
  • ABCD實現I
  • 使用枚舉valueOf和對象overriding來刪除case語句。
  • 使用多態並從所有方法返回I
  • 下面是代碼(不包括ABCDI ),因為它們是普通類和接口 - 沒有做太多。

以下是代碼:

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;

}

最好將get與未來分開,因此將一個Callable作為參數添加到枚舉中。 然后,枚舉瞬間可以創造一個未來。 不幸的是,對於通用類型,需要存儲生成的類,並用於正確鍵入。

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

如果這些是您提交給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);
}

如果cachePool有其他不相關的任務或由於某些其他原因你不能使用awaitTermination那么你可以阻塞信號量 使用零許可初始化Semaphore ,每個任務在完成時release許可,並且dispatch方法在semaphore.acquire(methodNames.size())semaphore.acquire(methodNames.size()) ,等待所有任務調用release (並因此完成)。 注意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());
}

如果你正在收集任務的結果(看起來你現在看起來不像這樣,但需求往往會改變),那么每個Runnable都可以將其結果存儲在共享的ConcurrentLinkedQueue或其他一些線程安全的數據中結構(或每個返回類型的一個數據結構等),然后當semaphore.acquireawaitTermination方法解除awaitTermination時, dispatch可以處理這些結果。

這些成分

您需要以多個步驟處理結果:

等待多個期貨

在這種情況下,您可以在簽名中使用Future<?> ,因為只是等待您不必了解結果類型。 所以你可以創建一個方法void waitForAll(List< Future<?> > futures)

以安全的方式從未知的未來獲得結果

為此你需要一些知道Future將提供的類型的句柄。 由於Java的類型擦除,這個句柄必須以某種方式存儲Class<T> 所以最簡單的句柄就是相應的Future<T>本身(在你的例子中TAB之一)。

因此,您可以將期貨存儲在Map<Class<?>, Future<?>) (或MultiMap )中,並使用Future<T> get<T>(Class<T> handle)類型的附加get方法。

您可以使用此句柄替換enum MethodNames

收據:將成分與解決方案相結合

  1. 創建如上定義的Map / MultiMap ,例如通過取消對dispatch方法的多次調用。
  2. 將waitAll與地圖的值列表一起使用
  3. 使用上述get方法從Map / MultiMap獲取相應的結果

你正在嘗試做一些像fork-join或map-reduce這樣的東西。 您可能會找到一種既定機制來完成此任務,而不是重新發明輪子。

無論如何回到你等待所有方法完成和繼續前進的具體問題

正如你所提到的,你不應該失去指向未來的指針。 因此,創建一個結構Result ,它可以容納所有未來。 等待當前線程中的Result Result中運行另一個線程,它將監視期貨並在返回所有方法時通知

Result通知時,您將在當前線程中向前移動,並返回Result對象保存的所有返回數據。

簡單(有限)解決方案 :如果您可以為返回值定義接口/超類(類似於sql.ResultSet ),這非常有用,否則沒有那么多......然后在處理結果時再次出現切換因為你必須施展它。

調度:

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

接口:

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

類示例:

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

調度方式:

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