【多线程】Java多线程基础(15)- 使用Future

47 阅读3分钟

Future

Future是什么玩意

正如Future的名字,它表示未来可能获得的对象(线程任务submit的返回对象)

Java Future接口是Java多线程编程中的一个接口,它用于表示异步计算的结果。在Java中,通常使用多线程来执行耗时的操作,但是由于多线程中的线程是并发执行的,因此无法保证操作的完成时间。为了解决这个问题,Java提供了Future接口,它可以让我们在等待操作完成时可以继续执行其他的任务,从而提高程序的效率。

Future接口定义了以下方法:

  1. boolean cancel(boolean mayInterruptIfRunning):尝试取消执行任务,如果任务已经完成或已经被取消,则返回false。
  2. boolean isCancelled():如果任务已经被取消,则返回true。
  3. boolean isDone():如果任务已经完成,则返回true。
  4. V get() throws InterruptedExceptionExecutionException:获取任务的计算结果,如果任务还没有完成,则阻塞当前线程,直到任务完成为止。
  5. V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException:获取任务的计算结果,如果任务还没有完成,则阻塞当前线程,直到任务完成或等待时间超时为止。

在Java中,有多种方式可以创建Future接口的实现类,例如使用ExecutorService.submit(Runnable/Callable)方法提交一个线程任务,返回一个Future对象,通过这个对象可以获取线程任务的执行结果。

总之,Future接口是Java多线程编程中非常重要的一个接口,它可以帮助我们实现异步计算,提高程序的效率。


我们可以用Future来get()任务返回的结果

但是有一个问题, 我线程执行run()方法,而run()方法并没有返回值,那我获取什么?

所以我们引入了Callable接口

Callable接口

Callable接口可以用于在一个线程中执行某个任务,并返回执行结果。与Runnable接口不同的是,Callable接口可以返回一个值,而Runnable接口不能。

创建一个实现了Callable接口的类,并重写call()方法,该方法返回一个泛型类型的值。

用它替换Runnable接口, 可以运行任务并获得它的返回值(注意是线程池的环境)。

使用Callable接口的案例

public class Main {
    public static void main(String[] args) {
        // 创建一个单线程的线程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        Future<Integer> submit = es.submit(new Counter(2, 5));
        try {
            // 主线程调用get,被阻塞。
            System.out.println("计算得到的结果:"+submit.get());
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
        es.shutdown();
    }
}

class Counter implements Callable<Integer> {
    private int a;
    private int b;
    Counter(int a, int b){
        this.a = a;
        this.b = b;
    }
    // 实现Callable接口
    @Override
    public Integer call(){
        return a+b;
    }
}

输出:

计算得到的结果:7

// 注意,Future对象的get()方法是阻塞的,任务执行完毕并获得结果才会进行下一步操作。
比如上面的案例,会将主线程阻塞。

改进阻塞

如果你希望同时等待多个任务的执行完成并获取结果,可以使用 invokeAll() 方法来一次性提交多个任务,并等待它们的执行完成。这样可以避免在等待每个任务的完成时,阻塞线程池中的线程,从而提高代码的执行效率。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(5);

        List<Callable<Integer>> tasks = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            tasks.add(new Counter(2, 5));
        }

        List<Future<Integer>> results = es.invokeAll(tasks);
        for (Future<Integer> result : results) {
            try {
                System.out.println("计算得到的结果:" + result.get());
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }

        System.out.println("主线程继续执行");
        es.shutdown();
    }
}

class Counter implements Callable<Integer> {
    private int a;
    private int b;
    Counter(int a, int b){
        this.a = a;
        this.b = b;
    }
    // 实现Callable接口
    @Override
    public Integer call(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return a+b;
    }
}

上面的例子不会阻塞,可以一次性执行任务。