大话Android多线程(四) Callable、Future和FutureTask

6,797 阅读5分钟

版权声明:本文为博主原创文章,未经博主允许不得转载
源码:github.com/AnliaLee
大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论

前言

大话Android多线程(一) 一文中,我们聊了创建线程的两种方式(继承Thread和实现Runnable接口),并比对了它们的区别。本章我们将介绍第三种方式 —— 通过实现Callable接口来创建线程

往期回顾
大话Android多线程(一) Thread和Runnable的联系和区别
大话Android多线程(二) synchronized使用解析
大话Android多线程(三) 线程间的通信机制之Handler


实现Callable接口创建线程

我们先简单举个栗子,看看通过实现Callable接口来创建线程的方式和之前两种有什么区别

某日,高铁站前,老C和他儿子道别,儿子:“爸爸,你走吧。”老C望了望路边的小摊,说道

说完老C便走去小摊买橘子了(实现Callable接口,重写call()方法)

public static class TestCallable implements Callable{
	@Override
	public String call() throws Exception {
		System.out.println(Thread.currentThread().getName() + ":我买几个橘子去。你就在此地,不要走动" + " 时间:" + getTime());
		Thread.sleep(2000);//模拟买橘子的时间
		return Thread.currentThread().getName() + ":我买完橘子回来了" + " 时间:" + getTime();
	}
}

儿子自然是乖乖站在原地等爸爸买橘子

public class CallableTest {
    //省略部分代码...
    public static void main(String args[]){
        TestCallable callable = new TestCallable();
        FutureTask<String> futureTask = new FutureTask<String>(callable);
		
        Thread thread1 = new Thread(futureTask, "爸爸");
        thread1.start();

        System.out.println("儿子还没收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
        try{
            System.out.println(futureTask.get());
            System.out.println("儿子收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
        }catch (InterruptedException | ExecutionException e){
        }
    }
}

正常买到橘子的运行结果如下

如果没买到橘子呢(我们尝试在call()方法中抛出异常,然后在调用get()方法时进行捕获)?

public static class TestCallable implements Callable{
	private int ticket = 10;

	@Override
	public String call() throws Exception {
		System.out.println(Thread.currentThread().getName() + ":我买几个橘子去。你就在此地,不要走动" + " 时间:" + getTime());
		Thread.sleep(2000);//模拟买橘子的时间
		System.out.println(Thread.currentThread().getName() + ":橘子卖完了" + " 时间:" + getTime());
		
		throw new NullPointerException("橘子卖完了");
	}
}

public static void main(String args[]){
	TestCallable callable = new TestCallable();
	FutureTask<String> futureTask = new FutureTask<String>(callable);

	Thread thread1 = new Thread(futureTask, "爸爸");
	thread1.start();

	System.out.println("儿子站在原地" + " 时间:" + getTime());//验证主线程的执行情况
	try{
		System.out.println(futureTask.get());
		System.out.println("儿子收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
	}catch (InterruptedException | ExecutionException e){
		System.out.println("儿子没收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
	}
}

另外需要注意的是,爸爸的钱只够买一袋橘子(任务只能执行一次)

public static void main(String args[]){
	TestCallable callable = new TestCallable();
	FutureTask<String> futureTask = new FutureTask<String>(callable);

	Thread thread1 = new Thread(futureTask, "爸爸去了第一个摊位");
	Thread thread2 = new Thread(futureTask, "爸爸去了第二个摊位");
	Thread thread3 = new Thread(futureTask, "爸爸去了第三个摊位");

	thread1.start();
	thread2.start();
	thread3.start();

	System.out.println("儿子站在原地" + " 时间:" + getTime());//验证主线程的执行情况
	try{
		System.out.println(futureTask.get());
		System.out.println("儿子收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
	}catch (InterruptedException | ExecutionException e){
		System.out.println("儿子没收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
	}
}

总结上面的案例:

  • Callable在被线程执行后,可以提供一个返回值,我们可以通过Futureget()方法拿到这个值

Future是一个接口,而FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口(继承关系见下图)

FutureTask用于异步获取执行结果或取消执行任务的场景,它的主要功能有:

  • 可以判断任务是否完成
  • 可以获取任务执行结果
  • 可以中断任务

更详细的源码解析及用法可以看下这几篇博客
Java并发编程:Callable、Future和FutureTask原理解析
Java FutureTask 源码分析 Android上的实现
FutureTask的用法及两种常用的使用场景

  • Callablecall()方法可以抛出异常,我们可以在尝试执行get()方法时捕获这个异常

区别于实现Runnable接口创建线程的方式,以上这两点功能Runnable就无法实现了

  • FutureTask可以确保任务只执行一次
  • 我们在某条线程执行get()方法时,该线程会被阻塞,直到Future拿到Callable.call()方法的返回值

UI线程中使用时(尤其是后续还有更新UI的操作)要特别注意这点,以免造成界面卡顿。那么要如何处理这种多线程执行耗时任务,等待结果,然后再更新UI的情况呢?没错!就是使用我们上一章讲到的Handler,Android系统提供的AsyncTask也正是用到了这一方式实现了异步操作,我们将在后续的章节详细介绍AsyncTask

此外,除了直接new一个Thread,我们还可以利用线程池结合Callable执行多线程任务

public static void main(String args[]){
	TestCallable callable = new TestCallable();
	ExecutorService executor = Executors.newCachedThreadPool();
	Future<String> future = executor.submit(callable);
	
	//或者
	//FutureTask<String> future = new FutureTask<String>(callable);
	//executor.execute(future);
	System.out.println("儿子站在原地" + " 时间:" + getTime());//验证主线程的执行情况
	try{
		System.out.println(future.get());
		System.out.println("儿子收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
	}catch (InterruptedException | ExecutionException e){
		System.out.println("儿子没收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
	}
}

那么线程池又是啥?留到下一章我们再“大话”一番吧

本篇博客到此结束,若大家有啥疑问或建议欢迎留言评论,感激不尽。如果觉得写得还不错麻烦点个赞,你们的支持是我最大的动力~