Executor, ExecutorService, Callable - 1

— Привет, Амиго!

Ничего нельзя создать идеальным с первого раза. Это касается и нитей. Со временем разработчики Java убедились, что интерфейс Runnable не идеален. Он не поддерживал перебрасывание исключений и не позволял узнать результат выполнения задачи…

Интерфейс Runnable скорее подходит для больших независимых задач, чем для маленьких подзадач, которых хочется запустить с десяток одновременно, а потом собрать с них результаты их работы.

Поэтому был придуман интерфейс Callable. Он гораздо лучше подходит для параллельного выполнения небольших задач, чем Runnable и Thread, еще и потому, что является generic-интерфейсом.

Вот типичный класс, который реализует интерфейс:

Пример
class ReverseString implements Callable<String>
{
 String str;

 ReverseString(String str)
 {
  this.str = str;
 }

 public String call() throws Exception 
 {
  StringBuilder builder = new StringBuilder(str);
  builder.reverse();
  return builder.toString();
 }
}

В отличие от Runnable, тут мы должны переопределить метод call, который возвращает результат, заданный типом-параметром. Такой подход гораздо удобнее, чем метод run интерфейса Runnable, который возвращает void. Иногда разработчикам приходилось придумывать различные «обходные пути», чтобы получить результат работы нити.

— Ясно.

— А теперь смотри, как Callable может работать в паре с ThreadPoolExecutor:

Во-первых, метод submit класса ThreadPoolExecutor возвращает параметризированный объект типа Future. Этот объект можно использовать, чтобы узнать, завершилось ли уже выполнение задачи, а также, чтобы получить результат ее выполнения.

Вот как это работает:

Пример
//1. Создаем ThreadPoolExecutor
ExecutorService service = Executors.newFixedThreadPool(5);

//2 помещаем в него задачу для выполнения
Future<String> task = service.submit(new ReverseString("Amigo")); 

//3 ждем пока задача выполнится
while(!task.isDone()) 
{
 Thread.sleep(1); 
}

//4 пробуем получить результат задачи 
//получим или результат или исключение, если оно было при выполнении задачи
try 
{
 System.out.println("Развернутая строка : " + task.get());
} 
catch (Exception ie) 
{ 
 ie.printStackTrace(System.err);
}

//5 останавливаем ThreadPool. 
service.shutdown();

— Круто! Особенно интерфейс Future понравился. А какие у него есть методы?

— Вот самые интересные:

Метод Описание
boolean cancel(boolean mayInterrupt);
Останавливает задачу.
boolean isCancelled();
Возвращает true, если задача была остановлена.
boolean isDone();
Возвращает true, если выполнение задачи завершено.
V get() throws InterruptedException, ExecutionException;
Возвращает результат вызова метода call или кидает исключение, если оно было.

— Круто! Так задачи еще и останавливать можно.

— Не сильно на это надейся – не каждую нить можно остановить. Но если задача еще в очереди, то это отлично сработает.

— Такой подход мне нравится. Гораздо удобнее, чем самому создавать нити и потом пробовать вытянуть из них результат.

— Отлично. На этом сегодня и закончим.