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 = новий 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 або кидає виняток, якщо він був.

— Круто! Так завдання ще й зупиняти можна.

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

— Такий підхід мені подобається. Набагато зручніше, ніж самому створювати нитки і пробувати витягнути з них результат.

— Чудово. На цьому сьогодні й закінчимо.