Перейти к контенту

Интерфейсы Callable и Future в Java

Интерфейс Java Callable(java.util.concurrent.Callable) представляет асинхронную задачу, которая может быть выполнена отдельным потоком. Например, можно передать объект Callable в Java ExecutorService, который затем выполнит его асинхронно. Метод call() вызывается для выполнения асинхронной задачи.

Интерфейс Callable довольно прост. Он содержит единственный метод с именем call().

 

public interface Callable {

    V call() throws Exception;

}

Если задача выполняется асинхронно, результат обычно передается обратно через Java Future. Это тот случай, когда Callable передается в ExecutorService для одновременного выполнения.

методы интерфейса future в java

Callable использует Generic для определения типа возвращаемого объекта. Класс Executors предоставляет полезные методы для выполнения Java Callable в пуле потоков. Поскольку вызываемые задачи выполняются параллельно, нам нужно дождаться возвращенного объекта.

Callable задачи возвращают объект java.util.concurrent.Future. Используя объект Java Future, мы можем узнать состояние задачи Callable и получить возвращенный объект. Он предоставляет метод get(), который может ожидать завершения Callable и затем возвращать результат.

Future предоставляет метод cancel() для отмены связанной задачи Callable. Существует версия метода get(), в которой мы можем указать время ожидания результата, поэтому полезно избегать блокировки текущего потока на более длительное время.

Существуют методы isDone() и isCancelled() для определения текущего состояния связанной вызываемой задачи.

Вот простой пример задачи с Callable, которая возвращает имя потока, выполняющего задачу через одну секунду. Мы используем платформу Executor для параллельного выполнения 100 задач и используем Java Future для получения результата представленных задач.

package com.journaldev.threads;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MyCallable implements Callable {

    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        //вернуть имя потока, выполняющего вызываемую задачу
        return Thread.currentThread().getName();
    }
    
    public static void main(String args[]){
        //Получить ExecutorService из служебного класса Executors
        //размер пула потоков равен 10
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //создать список для хранения объекта Future, связанного с Callable
        List<Future> list = new ArrayList<Future>();
        //Create MyCallable instance
        Callable callable = new MyCallable();
        for(int i=0; i< 100; i++){
            Future future = executor.submit(callable);
            //добавив Future в список, мы можем получить возвращаемое значение
            list.add(future);
        }
        for(Future fut : list){
            try {
// выводим возвращаемое значение Future, замечаем задержку вывода в консоли
// потому что Future.get() ожидает завершения задачи
                System.out.println(new Date()+ "::"+fut.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //закрыть службу
        executor.shutdown();
    }

}

После того, как мы выполним вышеуказанную программу, вы заметите задержку вывода, потому что метод get() ожидает завершения задачи, вызываемой Java. Также обратите внимание, что есть только 10 потоков, выполняющих эти задачи.

Вот фрагмент вывода вышеуказанной программы.

Mon Dec 31 20:40:15 PST 2012::pool-1-thread-1
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-3
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-4
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-5
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-6
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-7
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-8
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-9
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-10
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
...

Что делать, если мы хотим переопределить некоторые методы, например, переопределить метод get() для тайм-аута через некоторое время по умолчанию, а не ждать бесконечно?

В этом случае пригодится класс Java FutureTask, который является базовой реализацией Future.

package com.journaldev.threads;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FutureTaskExample {

public static void main(String[] args) {
	MyCallable callable1 = new MyCallable(1000);
	MyCallable callable2 = new MyCallable(2000);

	FutureTask futureTask1 = new FutureTask(callable1);
	FutureTask futureTask2 = new FutureTask(callable2);

	ExecutorService executor = Executors.newFixedThreadPool(2);
	executor.execute(futureTask1);
	executor.execute(futureTask2);
		
	while (true) {
		try {
		if(futureTask1.isDone() && futureTask2.isDone()){
			System.out.println("Done");
			executor.shutdown();
			return;
		}
				
		if(!futureTask1.isDone()){
		//ожидаем неопределенно долгое время для завершения задачи
		System.out.println("FutureTask1 output="+futureTask1.get());
		}
			
		System.out.println("Waiting for FutureTask2 to complete");
		String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
		if(s !=null){
			System.out.println("FutureTask2 output="+s);
		}
	} catch (InterruptedException | ExecutionException e) {
		e.printStackTrace();
	}catch(TimeoutException e){
		
	}
}
		
}
}

Callable против Runnable

Callable похож на Runnable в том, что оба они представляют задачу, которая предназначена для одновременного выполнения отдельным потоком.

Callable отличается от Runnable тем, что метод run() не возвращает значение и не может генерировать исключения (только RuntimeExceptions).

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

Оцени статью

Средняя оценка / 5. Количество голосов:

Спасибо, помогите другим - напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

 

Пока нет комментариев.

Добавить комментарий

Ваш e-mail не будет опубликован.

СайдбарКомментарии (0)