Как Марк и другие правильно ответили, что Future
- это интерфейс для FutureTask
, а Executor
- фактически его фабрика; Это означает, что код приложения редко создает экземпляр FutureTask
напрямую. В дополнение к обсуждению я привожу пример, показывающий ситуацию, в которой FutureTask
создается и используется напрямую, вне любых Executor
:
FutureTask<Integer> task = new FutureTask<Integer>(()-> {
System.out.println("Pretend that something complicated is computed");
Thread.sleep(1000);
return 42;
});
Thread t1 = new Thread(()->{
try {
int r = task.get();
System.out.println("Result is " + r);
} catch (InterruptedException | ExecutionException e) {}
});
Thread t2 = new Thread(()->{
try {
int r = task.get();
System.out.println("Result is " + r);
} catch (InterruptedException | ExecutionException e) {}
});
Thread t3 = new Thread(()->{
try {
int r = task.get();
System.out.println("Result is " + r);
} catch (InterruptedException | ExecutionException e) {}
});
System.out.println("Several threads are going to wait until computations is ready");
t1.start();
t2.start();
t3.start();
task.run(); // let the main thread to compute the value
Здесь FutureTask
используется в качестве инструмента синхронизации, например CountdownLatch
или аналогичного барьерного примитива. Это можно было бы повторно реализовать, используя CountdownLatch
или блокировки и условия; FutureTask
просто делает его красиво инкапсулированным, не требующим пояснений, элегантным и с меньшим количеством кода.
Также обратите внимание, что метод FutureTask # run () должен вызываться явно в любом из потоков; нет исполнителя, чтобы сделать это за вас. В моем коде он в конечном итоге выполняется основным потоком, но можно изменить метод get()
, чтобы он вызывал run()
в первом потоке, вызывая get()
, поэтому первый поток достигает get()
, и это может быть любой из T1, T2 или T3 будут выполнять вычисления для всех оставшихся потоков.
По этой идее - первый поток, запрашивающий результат, будет выполнять вычисления для других, в то время как одновременные попытки будут блокироваться - основан на Memoizer, см. Пример Memoizer Cache на стр. 108 в «Параллельности Java на практике».