创建线程的第三种方式——实现Callable接口
package com.lqy.Multithreading;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
创建线程的第三种方法:实现Callable接口
实现Callable接口是创建线程的三种方法中唯一一个带有返回值的方法
步骤:
1、实现Callable接口
2、重写里面的Call方法(注意是Call不是Run)
3、创建Callable实现类的对象
4、将实现类对象作为参数传递给FutureTask构造函数
5、将FutureTask对象作为参数传递给Thread构造函数(因为FutureTask实现了Runnable接口,所以可以这么传)
6、调用Thread类的start方法
这种创建线程的方法一般用于需要很长时间的操作但是我们只需要结果的情况
比如我们需要进行一个算法运算,这个运算的过程非常繁琐,需要很长的时间才能完成,但是我们最终只需要它的运行结果而已
那么我们就可以先在要获取结果前多一段时间就运行该线程,在最终需要结果的地方去获取结果
但是注意,如果执行了获取结果的方法但是那个线程还没有得到结果的话,线程会阻塞直到执行结果出来
*/
public class CallableImpl implements Callable {
/*
在重写Call方法的时候注意
前面的返回类型你可以自己修改,但是在创建FutureTask类的时候里面的泛型要与之相同
*/
@Override
public Object call() throws Exception {
System.out.println("CallableImpl");
return "我是Call方法的返回值";
}
public static void main(String[] args) {
CallableImpl callable=new CallableImpl();
FutureTask<Object> futureTask=new FutureTask<>(callable);
Thread thread=new Thread(futureTask);
/*
需要注意一件事:
FutureTask类中的get方法获取返回值只能执行一次
而且,如果使用了这个方法但是线程还没有运行到可以返回的那行代码,那么就会一直阻塞
比如如果我在这里执行了如下代码:
Object result=futureTask.get();
那么就永远阻塞了
当然,我更想说的是,如果你使用的是这种方法创建线程并且需要返回值的话,里面就别写死循环
否则就是死锁在召唤
*/
thread.start();
try {
Object result=futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
我们来看看Callable类
![](https://img-blog.csdnimg.cn/b08e82d933cd405e94dd6172d9e876fb.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAanVzdF9zdGlsbF9hd2F5,size_15,color_FFFFFF,t_70,g_se,x_16)
很显然,它和Runnable类一样,是个功能接口,里面也只有一个方法。
我们接下来看看FutureTask类。
通过一路的追踪源码我们可以知道:
FuterueTask extends RunnableFuture
RunnableFuture extends Runnable,Future
所以我们先来看看Future类
通过查看源码我们可以发现,Future是一个接口,其中定义的接口方法有:
注意,这里只是定义了这些方法,并没有实现,实现这些方法是在FutureTask类中。
接下来我们来看RunnableFuture类
![](https://img-blog.csdnimg.cn/1d9c4ce303454d05b15f5783ecd5d958.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAanVzdF9zdGlsbF9hd2F5,size_19,color_FFFFFF,t_70,g_se,x_16)
可以发现,它继承了Runnable类和Future类,但是并没有实现他们(自己定义为接口,那么就可以不用实现接口的方法)。
最后来到FutureTask中,根据Java继承的特性可知,Future间接的继承了Runnable类和Future类。
也就是说FutureTask类要实现Run方法和Future类中定义的所有方法。
我们先来看看构造方法:
可以发现,它有两个构造方法:
我们再来看看这个类中的一些方法:
获取线程的允许情况我们可以直接通过状态码知道:
![](https://img-blog.csdnimg.cn/38735ac736bf48ddbb8b112a1fb1949f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAanVzdF9zdGlsbF9hd2F5,size_17,color_FFFFFF,t_70,g_se,x_16)
有一个颇有意思的方法:
我们来实验一下:
package com.lqy.Multithreading;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class FutureTaskTest implements Callable {
private int i=0;
//发现了个有意思的事,它的返回值就算不写也不会报错
@Override
public Object call() throws Exception {
while (true){
System.out.println(i++);
}
}
public static void main(String[] args) {
FutureTaskTest futureTaskTest=new FutureTaskTest();
FutureTask futureTask=new FutureTask(futureTaskTest);
Thread thread=new Thread(futureTask);
thread.start();
//我们来看看cancel能不能停止该线程
futureTask.cancel(true);
}
}
运行结果:
可以发现,可以阻止线程运行。
接下来我们来看看我们最经常用的,获取运行结果的Get方法
![](https://img-blog.csdnimg.cn/036c2370d6e945eab5935f00998acac6.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAanVzdF9zdGlsbF9hd2F5,size_20,color_FFFFFF,t_70,g_se,x_16)
runAndReset方法会在线程池中使用到,等讲到线程池的时候在详细聊一聊吧。
总结的说:
三种方式:
第一种:Thread,实现了Runnable接口
第二种:Runnable,重写了本类的run方法,使用Thread类的start方法运行
第三种:Callable,重写了本类的call方法,但是作为接收参数的FutureTask间接继承了Runnable接口,其中的run方法实现是将重写的call方法放在run方法中,并加上相应的操作,最后调用Thread类的start方法。