Thread类、Runnable接口使得多线程编程简单直接。
但Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。
public void run()方法规范意味着你必须捕获并处理检查型异常。即使你小心捕获异常,也不能保证这个类(Runnable对象)的所有使用者都读取异常信息。
以上两个问题现在都得到了解决。从java5开始,提供了Callable接口,是Runable接口的增强版。用Call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。
1. Callable和Runable对比
先初步认识一下Callable接口:这是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象。
具体代码实现对比
class MyRunnableThread implements Runnable{
@Override
public void run() {
}
}
class MyCallableThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return null;
}
}
public class CallableDemo {
public static void main(String[] args) {
// 创建多线程
}
}
该如何使用Callable创建Thread对象,如果使用Runnable是:
public class CallableDemo {
public static void main(String[] args) {
// 创建多线程
new Thread(new MyRunnableThread(), "threadName").start();
}
}
现在能不能直接把MyRunnableThread换成MyCallableThread。当然不行,thread的构造方法参数需要Runnable类型的数据模型,而MyCallableThread属于Callable类型的。
那么到底怎么使用Callable创建thread对象呢?
2. Callable的使用
使用步骤:
创建Callable的实现类,并重写call()方法,该方法为线程执行体,并且该方法有返回值
创建Callable的实例。
实例化FutureTask类,参数为Callable接口实现类的对象,FutureTask封装了Callable对象call()方法的返回值
创建多线程Thread对象来启动线程,参数为FutureTask对象。
通过FutureTask类的对象的get()方法来获取线程结束后的返回值
这里出现了一个FutureTask,先认识该类,上源码:
发现:FutureTask其实充当了一个中间人的角色
/**
* 1. 创建Callable的实现类,并重写call()方法,该方法为线程执行体,并且该方法有返回值
*/
class MyCallableThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "执行了!");
return 200;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 2. 创建Callable的实例,并用FutureTask类来包装Callable对象
// 3. 创建FutureTask对象,需要一个Callable类型的参数
FutureTask task = new FutureTask<Integer>(new MyCallableThread());
// 4. 创建多线程,由于FutureTask的本质是Runnable的实现类,所以第一个参数可以直接使用task
new Thread(task, "threadName").start();
//new Thread(task, "threadName2").start();
/*while (!task.isDone()) {
System.out.println("wait...");
}*/
System.out.println(task.get());
System.out.println(Thread.currentThread().getName() + " over!");
}
}
创建方式一
//1、创建callable任务
Callable<String> c = () -> {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行了Callable任务");
return "hehe";
};
//2、创建FutureTask(Runnable的子类)
FutureTask<String> futureTask = new FutureTask(c);
//3、创建线程传入FutureTask并启动线程
new Thread(futureTask, "ff").start();
创建方式二
FutureTask<String> f1 = new FutureTask<>(() -> {
System.out.println("callable任务执行了");
return "hehe:" + new Date();
});
FutureTask<String> f2 = new FutureTask<>(() -> {
System.out.println("callable任务执行了");
return "hehe:" + new Date();
});
new Thread(f1).start();
new Thread(f2).start();
- FutureTask:未来的任务,用它就干一件事,异步调用。通常用它解决耗时任务,挂起堵塞问题。
- 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- FutureTask 仅在 call 方法完成时才能get结果;如果计算尚未完成,则阻塞 get 方法。
- 一旦计算完成,就不能再重新开始或取消计算。get方法获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
注意:
为了防止主线程阻塞,建议get方法放到最后
只计算一次,FutureTask 会复用之前计算过得结果
创建多个线程,会怎样?
运行结果:依然只有一个就是threadName。
如果想打印threadName2的结果,即不想复用之前的计算结果。怎么办?再创建一个FutureTask对象即可。