一、前言
我们都知道,线程的实现方式由三种:
继承Thread类、实现Runnable接口、实现Callable接口。
在Java基础–多线程【入门】中已经介绍了前面两种实现方式,本文主要总结第三种方式,一起往下学习吧。
二、Runnable和Callable的区别
实现 Runnable 和实现 Callable 接口的方式主要有以下两点区别:
1)Callable接口中call方法有返回值,而Runnable接口中的run方法没有返回值
2)Callable接口中call方法有声明异常,而Runnable接口中的run方法没有异常
三、Callable的使用方式一
方式一为:转换为FutureTask来运行Callable
下面我们通过案例来理解这种方式的使用:
(一)需求
使用Callable实现线程的方式求1-100的和
(二)实现步骤
1)创建Callable对象
2)将Callable对象转换为可执行任务 Futuretask对象
3)创建线程
4)启动线程
5)获取结果
(三)代码实现
1 | public class Demo01 { |
(四)代码讲解
步骤一:
使用匿名内部类的方式创建Callable对象,并传递泛型。并且该泛型值其实就是call方法的返回值类型。
步骤二:
1、转换成FutureTask的原因是:Thread中没有参数为Callable的构造方法。因此,无法直接通过Thread来启动,需要转换为Runnable接口的子类。
2、查看以下继承与实现关系:
FutureTaskimplements RunnableFuture –>
RunnableFutureextends Runnable, Future
会发现,其实FutureTask是Runnable的一个实现类,所以可以使用Thread来启动
步骤三:
通过 public Thread(Runnable target) 构造方法来创建线程对象
步骤五:
可以通过FutureTask的get()方法来获取call方法的返回值。注意:只有Callable中的call方法执行完毕get方法才能获取到返回值
四、Callable的使用方式二
方式二为:通过线程池来使用Callable接口实现线程
(一)需求
使用Callable实现线程的方式求1-100的和
(二)实现步骤
1)创建线程池
2)提交任务
3)获取任务结果
4)关闭线程池
(三)代码实现
1 | public class Demo02 { |
(四)代码讲解
步骤一:
newFixedThreadPool()方法创建指定线性个数的线程池
步骤二:
通过线程池的submit()方法提交任务,并通过Future接收返回值
步骤三:
使用get()方法从Future中获取返回值
五、Future接口
(一)Future接口概念
Future:表示将要完任务的结果(表示ExecutorService.submit()所返回的状态结果,就是call的返回值)
其中的 get()方法:以阻塞形式等待Future中的异步处理结果(call方法的返回值)
细心的小伙伴可能已经发现了,貌似前面的FutureTask的get方法和Future中的get方法是一样的,是不是存在什么关系呢?
确实是这样,通过查看源码我们会发现:
FutureTaskimplements RunnableFuture –> RunnableFuture
extends Runnable, Future
所以,其实 FutureTask 就是 Future的一个实现类。
(二)Future接口案例
下面通过一个案例来进一步熟悉Future接口的使用:
需求:使用两个线程,并发计算1-50、51-100的和,然后再进行汇总计算(使用线程池方式)
代码实现:
编写Callable接口实现类:
1 | class SumCallable implements Callable<Integer>{ |
通过线程池提交任务:
1 | public class Demo03 { |
(三)get方法
Future接口中的get()方法是同步的,也就是说在线程没有执行完毕前,处于阻塞状态。
只有在线程执行完毕后才能通过该方法获取call方法的返回值。
六、线程的同步与异步
(一)线程的同步:
(二)线程的异步:
七、总结
如果使用线程完成任务时,需要有返回值,那么可以考虑使用Callable接口来实现线程,否则,不推荐使用该方式。
Java新手,若有错误,欢迎指正!