Многопоточность Java — возможность вызова и будущее

Java

Причина появления Callable и Future

Есть два способа создать поток: один — напрямую наследовать Thread, а другой — реализовать интерфейс Runnable. У обоих этих двух методов есть недостаток: результат выполнения нельзя получить после выполнения задачи. Если вам нужно получить результат выполнения, вы должны совместно использовать переменные или использовать связь потоков для достижения эффекта, что более проблематично в использовании. Начиная с Java 1.5 были предусмотрены Callable и Future, через которые можно получить результат выполнения задачи после выполнения задачи.

Введение в Callable и Future

Интерфейс Callable представляет собой фрагмент кода, который может быть вызван и возвращает результат; интерфейс Future представляет собой асинхронную задачу, которая представляет собой будущий результат, заданный незавершенной задачей. Поэтому говорят, что Callable используется для получения результатов, а Future используется для получения результатов. Интерфейс Callable использует дженерики для определения типа возвращаемого значения. Класс Executors предоставляет несколько полезных методов для выполнения задач внутри Callable в пуле потоков. Поскольку задача Callable является параллельной (parallel — это общий вид параллельности, на самом деле в определенный момент времени выполняется только один поток), мы должны дождаться результата, который она вернет. Объект java.util.concurrent.Future решает эту проблему за нас. После того, как пул потоков отправляет задачу Callable, возвращается объект Future, который можно использовать для определения статуса задачи Callable и получения результата выполнения, возвращаемого Callable. Future предоставляет метод get(), так что мы можем дождаться завершения Callable и получить результат его выполнения.

Вызываемый и работающий

java.lang.Runnable, это интерфейс, в котором объявлен только один метод run():

public interface Runnable {
    public abstract void run();
}

  Поскольку возвращаемое значение метода run() имеет тип void, он не может возвращать никаких результатов после выполнения задачи.

 Callable находится в пакете java.util.concurrent, это тоже интерфейс, и в нем объявлен только один метод, но этот метод называется call():

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Это общий интерфейс, и тип, возвращаемый функцией call(), является типом переданного V.

Использование вызываемого

Как правило, он используется с ExecutorService, а в интерфейсе ExecutorService объявлено несколько перегруженных версий методов отправки.

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

Тип параметра в первом методе отправки — Callable.

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

Как правило, мы используем первый метод отправки и третий метод отправки, а второй метод отправки используется редко.

Future

Будущее — это отмена результата выполнения конкретной задачи Runnable или Callable, запрос о ее завершении и получение результата. При необходимости результат выполнения можно получить через метод get, который будет блокироваться до тех пор, пока задача не вернет результат.

Класс Future находится в пакете java.util.concurrent, который представляет собой интерфейс:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
В интерфейсе Future объявлено пять методов, и функция каждого метода объясняется по очереди ниже.
  • Метод отмены используется для отмены задачи. Он возвращает true, если задача отмены прошла успешно, и возвращает false, если задача отмены не удалась. Параметр mayInterruptIfRunning указывает, разрешено ли отменять задачу, которая выполняется, но не завершена.Если он установлен в значение true, это означает, что задача в процессе выполнения может быть отменена. Если задача была завершена, независимо от того, является ли mayInterruptIfRunning истинным или ложным, этот метод обязательно вернет false, то есть, если завершенная задача будет отменена, он вернет false; если задача выполняется, если для mayInterruptIfRunning установлено значение true , он вернет true, если для параметра mayInterruptIfRunning установлено значение false, то он вернет false; если задача не была выполнена, независимо от того, является ли mayInterruptIfRunning значением true или false, она должна вернуть значение true.

  • Метод isCancelled указывает, была ли задача успешно отменена, и возвращает значение true, если задача была успешно отменена до нормального завершения задачи.

  • Метод isDone указывает, была ли задача выполнена, и возвращает значение true, если задача выполнена;

  • Для получения результата выполнения используется метод get(), этот метод заблокируется и не вернется до тех пор, пока задача не будет выполнена;

  • get(long timeout, TimeUnit unit) используется для получения результата выполнения.Если результат не получен в течение указанного времени, он напрямую вернет null.

Будущее обеспечивает три функции:
  1. определить, выполнена ли задача;
  2. Возможность прерывать задачи;
  3. Может получить результат выполнения задачи.

Поскольку Future — это просто интерфейс, его нельзя использовать для непосредственного создания объектов, поэтому существует следующая задача FutureTask.

FutureTask

FutureTask реализует интерфейс RunnableFuture, который определяется следующим образом:
public interface RunnableFuture<V> extends Runnable, Future<V> {  
    void run();  
}

Видно, что этот интерфейс реализует интерфейсы Runnable и Future, а конкретную реализацию в интерфейсе реализует FutureTask. Два конструктора этого класса следующие:

public FutureTask(Callable<V> callable) {  
        if (callable == null)  
            throw new NullPointerException();  
        sync = new Sync(callable);  
    }  
    public FutureTask(Runnable runnable, V result) {  
        sync = new Sync(Executors.callable(runnable, result));  
    }

Два конструктора предоставляются, как указано выше, один принимает Callable в качестве параметра, а другой принимает Runnable в качестве параметра. Связь между этими классами очень гибкая для способа моделирования задачи, позволяя вам написать задачу как Callable на основе функции Runnable FutureTask (поскольку она реализует интерфейс Runnable), а затем инкапсулировать ее в вызываемый объект, который запланирован. исполнителем и может быть отменен при необходимости.

Очень важно, чтобы FutureTasks могли быть отправлены исполнителями. Методы, которые он предоставляет извне, в основном являются комбинацией интерфейсов Future и Runnable: get(), cancel, isDone(), isCancelled() и run(), а метод run() обычно вызывается исполнителем, мы в принципе этого не делаем. Его нужно вызвать напрямую.

Пример задачи будущего
public class MyCallable implements Callable<String> {  
    private long waitTime;   
    public MyCallable(int timeInMillis){   
        this.waitTime=timeInMillis;  
    }  
    @Override  
    public String call() throws Exception {  
        Thread.sleep(waitTime);  
        //return the thread name executing this callable task  
        return Thread.currentThread().getName();  
    }  

}
public class FutureTaskExample {  
     public static void main(String[] args) {  
        MyCallable callable1 = new MyCallable(1000);                       // 要执行的任务  
        MyCallable callable2 = new MyCallable(2000);  

        FutureTask<String> futureTask1 = new FutureTask<String>(callable1);// 将Callable写的任务封装到一个由执行者调度的FutureTask对象  
        FutureTask<String> futureTask2 = new FutureTask<String>(callable2);  

        ExecutorService executor = Executors.newFixedThreadPool(2);        // 创建线程池并返回ExecutorService实例  
        executor.execute(futureTask1);  // 执行任务  
        executor.execute(futureTask2);    

        while (true) {  
            try {  
                if(futureTask1.isDone() && futureTask2.isDone()){//  两个任务都完成  
                    System.out.println("Done");  
                    executor.shutdown();                          // 关闭线程池和服务   
                    return;  
                }  

                if(!futureTask1.isDone()){ // 任务1没有完成,会等待,直到任务完成  
                    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){  
                //do nothing  
            }  
        }  
    }  
}