Callable、Future和FutureTas浅析

创建线程的方式

Java 中想要创建一个线程,比较容易的两种实现方式是继承 Thread 类或者实现 Runnable 接口。

以实现 Runnable 接口为例:

public class MyRunnableThread implements Runnable{

    @Override
    public void run() {
        System.out.println("子线程开始执行");
    }

    public static void main(String[] args) {
        // 创建线程对象并启动
        new Thread(new MyRunnableThread()).start();// 执行后输出:子线程开始执行
    }
}

上边这段代码比较简单,但存在个问题,它的 run 方法没有返回值。如果我们想要获取返回值,还需要做一些额外的操作,比如下边的代码:

public class MyRunnableThread implements Runnable{

    /**
     * 用来保存 run 方法的返回结果
     */
    private volatile String result;

    @Override
    public void run() {

        System.out.println("子线程开始执行");
        try {
            // 假设执行一些任务花费的时长
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 执行完后,将结果保存到 result 中
        result = "OK";
    }

    /**
     * 获取返回结果
     * @return
     */
    public String getResult(){
        while(true){
            if(null != result){
                // 有结果后返回
                return result;
            }
            // 让出一下 CPU 时间片,过会再来看看
            Thread.yield();
        }

    }

    public static void main(String[] args) {
        // 创建线程对象并启动
        MyRunnableThread myRunnableThread = new MyRunnableThread();
        new Thread(myRunnableThread).start();

        long start = System.currentTimeMillis();
        System.out.println("getResult 返回结果:"+myRunnableThread.getResult());
        long end = System.currentTimeMillis();

        System.out.println("getResult 花费的时间:" + (end - start));
    }
}

main 方法执行后输出:

子线程开始执行
getResult 返回结果:OK
getResult 花费的时间:3008

如上边这段代码,如果想要一个返回结果,那么就需要将结果保存到变量,还要提供额外的方法读取,非常不便。

什么是 Callable 和 Future

Callable 接口类似于 Runnable 接口, Runnable 没有返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以有返回值:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。可以认为 Callable 是带有返回值的 Runnable 。
而 Future 类型的实例代表一个未来能获取结果的对象。所以说 Callable 用于产生结果,Future 用于获取结果。

通过 Callable 和 Future 来创建有返回值的线程

public class MyCallableThread implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("子线程开始执行");
        // 假设执行一些任务花费的时长
        Thread.sleep(3000);

        return "OK";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建 Callable 任务
        Callable callable = new MyCallableThread();

        // 将 Callable 任务提交给线程池去执行并拿到 Future
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Future<String> future = executor.submit(callable);

        long start = System.currentTimeMillis();

        // 从 Future 获取异步执行返回的结果
        System.out.println("返回结果:" + future.get());

        long end = System.currentTimeMillis();

        System.out.println("获取返回结果花费时间:" + (end-start));
        
		// 关闭线程池
        executor.shutdown();
    }

}

main 方法执行后输出:

子线程开始执行
返回结果:OK
获取返回结果花费时间:3002

上边这段代码就简单多了:

  1. 创建 Callable 类型的对象;
  2. 创建线程池;
  3. 将 Callable 类型对象提交给线程池执行,并得到 Future 对象;
  4. 获取结果。

这里在调用 Future 的 get() 方法时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么 get() 会阻塞,直到任务完成后才返回结果。

除了 get() 方法,Future 中还定义了其它一些方法:

  • boolean cancel(boolean mayInterruptIfRunning); 取消当前任务;
  • boolean isCancelled(); 判断任务是否被取消
  • boolean isDone(); 判断任务是否已完成
  • V get(); 获取结果(可能会等待)
  • V get(long timeout, TimeUnit unit); 获取结果,但只等待指定的时间;

什么是 FutureTask

FutureTask 是 Future 接口的实现类,也可以代表异步计算的结果。除了实现 Future 接口外,FutureTask 还实现了 Runnable 接口,因此,FutureTask 可以交给线程池去执行,也可以由调用线程直接执行。

FutureTask 有两个构造方法

public FutureTask(Callable<V> callable) {
	if (callable == null)
		throw new NullPointerException();
	this.callable = callable;
	this.state = NEW;
}

public FutureTask(Runnable runnable, V result) {
	this.callable = Executors.callable(runnable, result);
	this.state = NEW;
}

第一个构造方法传入的是一个 Callable 类型的对象,第二个构造方法传入的是一个 Runnable 类型的对象,但在内部又被 Executors 工厂类包装成了 Callable 类型对象。

通过 Callable 和 FutureTask 来创建有返回值的线程

public class MyCallableThread implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("子线程开始执行");
        // 假设执行一些任务花费的时长
        Thread.sleep(3000);

        return "OK";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建 Callable 任务
        Callable callable = new MyCallableThread();

        // 创建 FutureTask 任务
        FutureTask futureTask = new FutureTask(callable);
        // 开启线程去执行 FutureTask 任务
        new Thread(futureTask).start();

        long start = System.currentTimeMillis();

        // 从 Future 获取异步执行返回的结果
        System.out.println("返回结果:" + futureTask.get());

        long end = System.currentTimeMillis();

        System.out.println("获取返回结果花费时间:" + (end-start));
    }

}

main 方法执行后输出:

子线程开始执行
返回结果:OK
获取返回结果花费时间:3002

案例示范

假如我们调一个下单接口,接口中需要查询客户信息(耗时1s)、商品信息(耗时2s)、账户信息(耗时4s)等,如果按照串行化执行,执行完后,总共耗时:7s (1+2+4)。

示例代码如下:

public class Test {

    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();

        // 查询客户信息
        Object customer = getCustomer();
        // 查询商品信息
        Object goods = getGoods();
        // 查询账户信息
        Object account = getAccount();

        System.out.println("客户信息:" + customer);
        System.out.println("商品信息:" + goods);
        System.out.println("账户信息:" + account);

        long end = System.currentTimeMillis();

        System.out.println("花费时长:" + (end - start));

    }

    /**
     * 模拟查询客户信息
     */
    public static Object getCustomer() throws InterruptedException {
        // 假设查询需要花费1s
        Thread.sleep(1000);
        return "customerData";
    }

    /**
     * 模拟查询商品信息
     */
    public static Object getGoods() throws InterruptedException {
        // 假设查询需要花费2s
        Thread.sleep(2000);
        return "goodsData";
    }

    /**
     * 模拟查询账户信息
     */
    public static Object getAccount() throws InterruptedException {
        // 假设查询需要花费4s
        Thread.sleep(4000);
        return "accountData";
    }
}

main 方法执行后输出:

客户信息:customerData
商品信息:goodsData
账户信息:accountData
花费时长:7002

我们再来通过有返回值的多线程试下:

public class Test {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        long start = System.currentTimeMillis();

        // 创建一个定长线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 将查询操作提交给线程池执行
        Future<Object> customerFuture = executor.submit(Test::getCustomer);
        Future<Object> goodsFuture = executor.submit(Test::getGoods);
        Future<Object> accountFuture = executor.submit(Test::getAccount);

        // 获得客户信息
        Object customer = customerFuture.get();
        // 获得商品信息
        Object goods = goodsFuture.get();
        // 获得账户信息
        Object account = accountFuture.get();

        System.out.println("客户信息:" + customer);
        System.out.println("商品信息:" + goods);
        System.out.println("账户信息:" + account);

        long end = System.currentTimeMillis();

        // 关闭线程池
        executor.shutdown();

        System.out.println("花费时长:" + (end - start));

    }

    /**
     * 模拟查询客户信息
     */
    public static Object getCustomer() throws InterruptedException {
        // 假设查询需要花费1s
        Thread.sleep(1000);
        return "customerData";
    }

    /**
     * 模拟查询商品信息
     */
    public static Object getGoods() throws InterruptedException {
        // 假设查询需要花费2s
        Thread.sleep(2000);
        return "goodsData";
    }

    /**
     * 模拟查询账户信息
     */
    public static Object getAccount() throws InterruptedException {
        // 假设查询需要花费4s
        Thread.sleep(4000);
        return "accountData";
    }
}

main 方法执行后输出:

客户信息:customerData
商品信息:goodsData
账户信息:accountData
花费时长:4058

通过输出可以看出,时间只花费了 4s。这比较容易理解,因为并行的时候,花费的时间只跟耗时最长的线程有关。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓呆同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值