天天看点

java并发-2 Callable和Future-多线程的返回值

上面介绍了多线程启动的两种方式,无论继承Thread还是实现Runnable接口,其内部的run方法都是void,也就是拿不到多线程的执行结果。

考虑下面一个小例子:有一个任务需要计算1-100的和,现代计算机肯定单线程分分钟就计算完成了,加一点限制条件,每执行一次加法操作,cpu都会暂停1秒,现在还觉得单线程计算很快么?现在就需要多线程来计算,比如5个线程同时计算,那么理论上不考虑其他因素,只需要20m即可计算完成。

此时不能继承Thread和实现Runnable接口来实现,因此计算后的返回值获取不到,这时Callable和Future就登场了,可以采用两种方式来实现:

1、使用ExecuteService线程池配置Callable和Future接口来实现,在执行Callable任务后,可以获取一个Future对象,调用其get方法,就可以获取到Callable任务的执行结果;Future是一个接口,提供了三种功能:

  • 判断任务是否完成
  • 中断任务
  • 获取任务结果

这里我们只关心任务结果,即get方法,代码如下(简便起见,停顿时间为100ms,线程数为2):

public class TestCallable {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        ExecutorService pool = Executors.newCachedThreadPool();
        CalculateTask task1 = new CalculateTask(1,50);
        Future<Integer> future1 = pool.submit(task1);
        CalculateTask task2 = new CalculateTask(50,101);
        Future<Integer> future2 = pool.submit(task2);
        pool.shutdown();

        System.out.println("主线程在执行任务");

        //这个get方法会阻塞,直到线程结束后返回结果
        try {
            int r1 = future1.get();
            int r2 = future2.get();
            int result = r1+r2;
            System.out.println("多线程执行结果:" + result);
            long end = System.currentTimeMillis();
            System.out.println("程序执行时间:" + (end-startTime)/1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 通过实现带泛型的Callable接口就可以将执行后的返回值返回给上层
 * 上层通过ExecutorService.submit执行后就能获得Future对象,
 * 该对象的get方法就能获取多线程执行后的泛型值
 */
class CalculateTask implements Callable<Integer> {

    private int start;
    private int end;

    public CalculateTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Integer call() throws Exception {
        System.out.println("线程" + Thread.currentThread().getName() + " 开启");
        int sum = 0;
        for(int i=start; i<end;i++) {
            sum += i;
            //1秒太长了,500吧
            Thread.sleep(100);
        }
        return sum;
    }
}
           

注意,get方法是一个阻塞方法,会等到子线程执行完成,才会继续执行,执行结果:

线程pool-1-thread-1 开启
线程pool-1-thread-2 开启
主线程在执行任务
多线程执行结果:5050
程序执行时间:5

Process finished with exit code 0
           

第二种,上面的方案是基于线程池进行的提交操作,如果不想用线程池怎么办?可以使用FutureTask类来实现,FutureTask类实现了RunnableFuture接口,RunnableFuture接口继承了Runnable接口和Future接口,FutureTask是Future接口唯一的实现类;

FutureTask实现了Future接口,就可以调用其get方法,获取返回结果;

FutureTask实现了Runnable接口,就可以把它传入Thread类中执行,这样就躲开了线程池;当然Future由于间接实现了Future,用线程池的submit也是可以的。

public class TestFutureTask {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        //第一种方式,通过线程池提交任务,简便起见,直接计算1-100了
        ExecutorService pool = Executors.newCachedThreadPool();
        CalculateTask task1 = new CalculateTask(1,50);
        FutureTask<Integer> futureTask1 = new FutureTask<Integer>(task1);
        CalculateTask task2 = new CalculateTask(50,101);
        FutureTask<Integer> futureTask2 = new FutureTask<Integer>(task2);
        pool.submit(futureTask1);
        pool.submit(futureTask2);
        pool.shutdown();

        /*第二种方式,由于FutureTask实现了RunnableFuture,而FutureRunnable接口
        * 而FutureRunnable接口又继承了Future和Runnable接口
        * 因此,可以通过Thread的方式启动
        */
        FutureTask<Integer> futureTask3 = new FutureTask<Integer>(task1);
        Thread thread1 = new Thread(futureTask3);
        thread1.start();
        FutureTask<Integer> futureTask4 = new FutureTask<Integer>(task2);
        Thread thread2 = new Thread(futureTask4);
        thread2.start();
        System.out.println("主线程执行任务");
        try {
            System.out.println("第一种方式获取计算结果:" + (futureTask1.get() + futureTask2.get()));
            System.out.println("第二种方式获取计算结果" + (futureTask3.get() + futureTask4.get()));
            long end = System.currentTimeMillis();
            System.out.println("程序执行时间:" + (end-startTime)/1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
           

以上代码提供了两种方式,使用线程池和使用Thread的方式启动,结果是相同的:

线程pool-1-thread-1 开启
线程pool-1-thread-2 开启
线程Thread-0 开启
主线程执行任务
线程Thread-1 开启
第一种方式获取计算结果:5050
第二种方式获取计算结果5050
程序执行时间:5
           

继续阅读