前面介绍了如何利用Runnable接口构建线程任务,该方式确实方便了线程代码的复用与共享,然而Runnable不像公共方法那样有返回值,也就没法将线程代码的处理结果传给外部,形成外部既不知晓该线程是否已经执行完毕,也不了解该线程的运算结果是什么,总之没法跟踪分线程的行动踪影。这里显然是不完美的,调用方法都有返回值,为什么经过Runnable启动线程就没法得到返回值呢?为此Java又提供了另外一种开启线程的方式,即利用Callable接口构建任务代码,实现该接口须要重写call方法,call方法相似run方法,一样存放着开发者定义的任务代码。不一样的是,run方法没有返回值,而call方法支持定义输出参数,从而在设计上保留了分线程在运行结束时返回结果的可能性。
Callable是个泛型接口,它的返回值类型在外部调用时指定,要想建立一个Callable实例,既能经过定义完整的新类来实现,也能经过普通的匿名内部类实现。举个简单的应用例子,好比但愿开启分线程生成某个随机数,并将随机数返回给主线程,则采起匿名内部类方式书写的Callable定义代码以下所示:html
// 定义一个Callable代码段,返回100之内的随机整数 // 第一种方式:采起匿名内部类方式书写 Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() { // 返回值为Integer类型 int random = new Random().nextInt(100); // 获取100之内的随机整数 // 如下打印随机很多天志,包括当前时间、当前线程、随机数值等信息 PrintUtils.print(Thread.currentThread().getName(), "任务生成的随机数="+random); return random; } };
因为Callable又是个函数式接口,所以可以利用Lambda表达式来简化匿名内部类,因而掐头去尾后的Lambda表达式代码示例以下:dom
// 第二种方式:采起Lambda表达式书写 Callable<Integer> callable = () -> { int random = new Random().nextInt(100); PrintUtils.print(Thread.currentThread().getName(), "任务生成的随机数="+random); return random; };
由于获取随机数的关键代码仅有一行,因此彻底能够进一步精简Lambda表达式的代码,压缩冗余后只有下面短小精悍的一行代码:ide
// 第三种方式:进一步精简后的Lambda表达式 Callable<Integer> callable = () -> new Random().nextInt(100);
有了Callable实例以后,还须要引入将来任务FutureTask把它包装一下,由于只有FutureTask才能真正跟踪任务的执行状态。如下是FutureTask的主要方法说明:
run:启动将来任务。
get:获取将来任务的执行结果。
isDone:判断将来任务是否执行完毕。
cancel:取消将来任务。
isCancelled:判断将来任务是否取消。
如今结合Callable与FutureTask,串起来运行一下拥有返回值的将来任务,首先把Callable实例填进FutureTask的构造方法,由此获得一个将来任务的实例;接着调用将来任务的run方法启动该任务,最后调用将来任务的get方法获取任务的执行结果。根据以上步骤编写的将来任务代码以下所示:函数
// 根据代码段实例建立一个将来任务 FutureTask<Integer> future = new FutureTask<Integer>(callable); future.run(); // 运行将来任务 try { Integer result = future.get(); // 获取将来任务的执行结果 PrintUtils.print(Thread.currentThread().getName(), "主线程的执行结果="+result); } catch (InterruptedException | ExecutionException e) { // get方法会一直等到将来任务的执行完成,因为等待期间可能收到中断信号,所以这里得捕捉中断异常 e.printStackTrace(); }
运行上述的任务调用代码,观察到的任务日志以下:线程
16:48:53.363 main 任务生成的随机数=11 16:48:53.422 main 主线程的执行结果=11
有没有发现什么不对劲的地方?第一行日志是在Callable实例的call方法中打印的,第二行日志是在主线程得到返回值后打印的,但是从日志看,两行日志都由main线程也就是主线程输出的,说明将来任务仍然由主线程执行,而非由分线程执行。
那么如何才能开启分线程来执行将来任务呢?固然还得让Thread类亲自出马了,就像使用分线程执行Runnable任务那样,一样要把Callable实例放入Thread的构造方法当中,而后再调用线程实例的start方法方可启动线程任务。因而添加线程类以后的将来任务代码变成了下面这样:设计
// 根据代码段实例建立一个将来任务 FutureTask<Integer> future = new FutureTask<Integer>(callable); // 把将来任务放入新建立的线程中,并启动分线程处理 new Thread(future).start(); try { Integer result = future.get(); // 获取将来任务的执行结果 PrintUtils.print(Thread.currentThread().getName(), "主线程的执行结果="+result); } catch (InterruptedException | ExecutionException e) { // get方法会一直等到将来任务的执行完成,因为等待期间可能收到中断信号,所以这里得捕捉中断异常 e.printStackTrace(); }
运行上面的将来任务代码,观察到如下的程序日志:日志
16:49:49.816 Thread-0 任务生成的随机数=38 16:49:49.820 main 主线程的执行结果=38
从日志中的Thread-0名称可知,此时的将来任务总算交由分线程执行了。htm
更多Java技术文章参见《Java开发笔记(序)章节目录》blog