满怀忧思,不如先干再说!做干净纯粹的技术分享,赞想点就点,注相关就关,有话评论区直接走起来!

在《Java并发编程》合集第一篇讲解线程创建时,说到创建线程有四种方式:

到这里全面性的讲解一下Callable接口,之前文章中使用Runnable接口创建线程,但是Runnable接口的run() 存在一个缺陷问题,就是不能将执行完的结果返回。

如果你对Callable很懵,不会用,想不到应用场景,只会背面试题,这篇文章你是来对了哦!

Java为了实现这个功能,在jdk1.5中提出了Callable接口。Callable任务可以有返回值,但是无法直接从Callable任务里获取返回值;需要使用Future来获取Callable任务的返回值。

所以Callable任务和Future模式,通常结合起来使用。学习Callable接口就必须也要学习Future接口。通过本文你可以掌握:

callable接口图解(别只会背面试题了)(1)

认识Callable

Callable 接口位于java.util.concurrent包下。此包简称JUC

@FunctionalInterface public interface Callable<V> { V call() throws Exception; //计算结果,如果无法计算则抛出异常。 }

发现Callable接口是一个泛型接口,并且使用@FunctionalInterface注解修饰,说明是一个函数式接口,其中包含一个call()抽象方法,拥有与泛型接口类型一致的返回值,并且可以抛出异常。

Callable配合FutureTask创建线程

需求:通过Callable接口实现线程创建,线程中实现语句输出,并返回整型结果

分析:

实现类

import java.util.concurrent.Callable; // 1、实现Callable接口,指定泛型类型为Integer public class CallableDemo implements Callable<Integer> { // 2、重写call方法,返回值类型与泛型类型一致 @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName() "线程运行......"); // 返回值,也就是线程的运算结果 return 28; } }

测试类

import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableMain { public static void main(String[] args) { // 1、创建实现类对象 CallableDemo callableDemo = new CallableDemo(); // 2、创建FutureTask对象 FutureTask<Integer> futureTask = new FutureTask<>(callableDemo); // 3、将futureTask当做Thread类参数传入 Thread t1 = new Thread(futureTask,"t1"); // 4、启动线程 t1.start(); // 5、通过futureTask的get方法获取返回值,有异常抛出 try { Integer result = futureTask.get(); System.out.println("线程执行结果:" result); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } } }

运行结果:

callable接口图解(别只会背面试题了)(2)

这里之所以要转成 FutureTask 放进 Thread中去,是因为Callable本身与Thread没有关系,通过FutureTask才能和Thread产生联系。

发现实现起来并不复杂,如果想要真正搞明白Callable就需要深入研究Future接口

Future接口

Future接口同样位于java.util.concurrent包下。下方为Future接口的源码,附加简单注释

public interface Future<V> { // 尝试取消此任务的执行。 boolean cancel(boolean mayInterruptIfRunning); // 如果此任务在正常完成之前被取消,则返回true boolean isCancelled(); // 如果此任务完成,则返回true 。 完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法将返回true boolean isDone(); // 获得任务计算结果 V get() throws InterruptedException, ExecutionException; // 可等待多少时间去获得任务计算结果 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }

Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。

实现

Future模式通俗点来描述就是:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。

import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableMain { public static void main(String[] args) { // 1、创建实现类对象 CallableDemo callableDemo = new CallableDemo(); // 2、创建FutureTask对象 FutureTask<Integer> futureTask = new FutureTask<>(callableDemo); // 3、将futureTask当做Thread类参数传入 Thread t1 = new Thread(futureTask,"t1"); // 4、启动线程 t1.start(); // 5、通过futureTask的get方法获取返回值,有异常抛出 try { Integer result = futureTask.get(); System.out.println("线程执行结果:" result); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } // 6、判断是否完成任务 boolean isDone = futureTask.isDone(); System.out.println(isDone); } }

运行结果:

callable接口图解(别只会背面试题了)(3)

Future 用于存储从另一个线程获得的结果。如果只是简单创建线程,直接使用Runnable就可以,想要获得任务返回值,就用Future。

FutureTask

位于java.util.concurrent包下,可取消的异步计算。 此类提供Future的基本实现,具有启动和取消计算、查询以查看计算是否完成以及检索计算结果的方法。 计算完成后才能检索结果; 如果计算尚未完成, get方法将阻塞。 一旦计算完成,就不能重新开始或取消计算【除非使用runAndReset调用计算】。该类继承结构图:

callable接口图解(别只会背面试题了)(4)

FutureTask源码

callable接口图解(别只会背面试题了)(5)

FutureTask构造方法

callable接口图解(别只会背面试题了)(6)

FutureTask构造可以接收Callable和Runnable。以此通过FutureTask为中介,将Callable和Thread关联起来

FutureTask应用场景及注意事项

你在什么时候使用过Callable呢?评论区告诉我们吧

应用场景:

注意

通过以下案例,演示FutureTask的方法作用:

import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class CallableMain { public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException { // 1、通过 lambda 创建FutureTask FutureTask<String> futureTask = new FutureTask<>(() -> { String str = ""; for (int i = 0; i < 10; i ) { str = String.valueOf(i); Thread.sleep(200); } return str; }); // 2、创建线程对象 Thread t1 = new Thread(futureTask,"t1"); t1.start(); // isDone():查看任务是否完成,完成返回true,否则是false System.out.println(futureTask.isDone()); // get():获取任务结果,如果任务没有执行完则阻塞 System.out.println("阻塞式获取结果:" futureTask.get()); // 取消任务,参数为true取消,false不取消 futureTask.cancel(true); } }

运行结果:发现程序阻塞,2秒后打印运算结果

callable接口图解(别只会背面试题了)(7)

如果使用get(long timeout, TimeUnit unit)超时等待方法,设置一个超时时间,如果还没有获取到结果,则抛出异常

比如,我们的任务需要执行2秒,这里设置等待1秒,没有拿到结果,抛出TimeoutException超时异常

callable接口图解(别只会背面试题了)(8)

cancel()中的false参数

此方法传入true会中断线程停止任务,传入false则会让线程正常执行至完成,难以理解传入false的作用,既然不会中断线程,那么这个cancel方法不就没有意义了吗?

简单来说,传入false参数只能取消还没有开始的任务,若任务已经开始了,就任由其运行下去。当创建了Future实例,任务可能有以下三种状态:

适用场景

Future.cancel(true)适用于:

Future.cancel(false)适用于:

Callable配合线程池创建线程

线程池ExecutorService类中的submit方法支持提交并运行线程,提供了Callable和Runnable的支持。

线程池文章并没有发布,下一篇开始线程池部分,这简单使用一下,不会线程池的同学,可以先查阅一下其他资料

callable接口图解(别只会背面试题了)(9)

实现方式

import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableAndThreadPool { public static void main(String[] args) throws ExecutionException, InterruptedException { // 1、创建线程池 ExecutorService threadExecutor = Executors.newSingleThreadExecutor(); // 2、提交线程任务 Future<Integer> future = threadExecutor.submit(() -> { System.out.println("线程池执行Callable线程"); return 1024; }); // 3、获取线程执行结果 Integer result = future.get(); System.out.println(result); // 4、关闭线程池,否则程序不停止 threadExecutor.shutdown(); } }

运行结果:

callable接口图解(别只会背面试题了)(10)

Runnable和Callable的区别Callable应用案例

案例:

系统中有社区功能,可以查看其他用户发布的说说,类似于朋友圈,还需获取每一个说说的点赞和评论信息,比如一页10条来说,需要访问21次数据库【10条说说1次查到,每个说说的点赞信息和评论信息各需要10次,共21次】,访问一次数据库按100ms计算,21次,累计时间为2.1s。这个响应时间,怕是无法令人满意的。可以通过异步化改造接口。

查出帖子列表后,迭代帖子列表,在循环里起10个线程,并发去获取每条帖子的点赞列表,同时另起10个线程,并发去获取每条帖子的评论列表。这样改造之后,接口的响应时间大大缩短,在200ms。这个时候就要用Callabel结合Future来实现。

分析:

说说类

import lombok.Data; import java.io.Serializable; import java.util.List; @Data public class Shuoshuo implements Serializable { private Long id; private String context; private List<Comment> comments; private List<ShuoshuoLike> shuoshuoLikes; public Shuoshuo(Long id, String context) { this.id = id; this.context = context; } }

评论类

import lombok.Data; import java.io.Serializable; @Data public class Comment implements Serializable { private Long id; private Long shuoshuoId; private String commentContent; public Comment(Long id, Long shuoshuoId, String commentContent) { this.id = id; this.shuoshuoId = shuoshuoId; this.commentContent = commentContent; } }

点赞类

import lombok.Data; import java.io.Serializable; @Data public class ShuoshuoLike implements Serializable { private Long id; private Long shuoshuoId; private Long userId; public ShuoshuoLike(Long id, Long shuoshuoId, Long userId) { this.id = id; this.shuoshuoId = shuoshuoId; this.userId = userId; } }

测试类

import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; public class ShuoshuoMain { // 1、获取说说 private static List<Shuoshuo> getShuoshuoList() { List<Shuoshuo> shuoshuos = new ArrayList<>(); shuoshuos.add(new Shuoshuo(1L,"说说1")); shuoshuos.add(new Shuoshuo(2L,"说说2")); shuoshuos.add(new Shuoshuo(3L,"说说3")); shuoshuos.add(new Shuoshuo(4L,"说说4")); return shuoshuos; } // 2、根据说说Id 获取评论 private static List<Comment> getComment(Long shuoshuoId) { List<Comment> comments = new ArrayList<>(); comments.add(new Comment(1L,1L,"1号说说很不错1")); comments.add(new Comment(2L,1L,"1号说说很不错2")); comments.add(new Comment(3L,1L,"1号说说很不错3")); comments.add(new Comment(4L,1L,"1号说说很不错4")); comments.add(new Comment(5L,1L,"1号说说很不错5")); comments.add(new Comment(6L,2L,"2号说说很不错1")); comments.add(new Comment(7L,2L,"2号说说很不错2")); comments.add(new Comment(8L,2L,"2号说说很不错3")); comments.add(new Comment(9L,2L,"2号说说很不错4")); comments.add(new Comment(10L,3L,"3号说说很不错1")); comments.add(new Comment(11L,3L,"3号说说很不错2")); comments.add(new Comment(12L,3L,"3号说说很不错3")); comments.add(new Comment(13L,4L,"4号说说很不错1")); comments.add(new Comment(14L,4L,"4号说说很不错2")); comments.add(new Comment(15L,4L,"4号说说很不错3")); // 使用stream过滤出指定id的评论 List<Comment> commentList = comments.stream().filter(item -> item.getShuoshuoId() == shuoshuoId).collect(Collectors.toList()); return commentList; } // 3、根据说说Id 获取点赞 private static List<ShuoshuoLike> getLike(Long shuoshuoId) { List<ShuoshuoLike> shuoshuoLikes = new ArrayList<>(); shuoshuoLikes.add(new ShuoshuoLike(1L,1L,101L)); shuoshuoLikes.add(new ShuoshuoLike(2L,1L,102L)); shuoshuoLikes.add(new ShuoshuoLike(3L,1L,103L)); shuoshuoLikes.add(new ShuoshuoLike(4L,1L,104L)); shuoshuoLikes.add(new ShuoshuoLike(5L,2L,105L)); shuoshuoLikes.add(new ShuoshuoLike(6L,2L,106L)); shuoshuoLikes.add(new ShuoshuoLike(7L,2L,107L)); shuoshuoLikes.add(new ShuoshuoLike(8L,2L,108L)); shuoshuoLikes.add(new ShuoshuoLike(9L,3L,101L)); shuoshuoLikes.add(new ShuoshuoLike(10L,3L,103L)); shuoshuoLikes.add(new ShuoshuoLike(11L,3L,104L)); shuoshuoLikes.add(new ShuoshuoLike(12L,3L,105L)); shuoshuoLikes.add(new ShuoshuoLike(13L,4L,105L)); shuoshuoLikes.add(new ShuoshuoLike(14L,4L,103L)); shuoshuoLikes.add(new ShuoshuoLike(15L,4L,104L)); shuoshuoLikes.add(new ShuoshuoLike(16L,4L,101L)); // 使用stream过滤出指定id的点赞信息 List<ShuoshuoLike> shuoshuoLikeList = shuoshuoLikes.stream().filter(item -> item.getShuoshuoId() == shuoshuoId).collect(Collectors.toList()); return shuoshuoLikeList; } // 4、主方法 public static void main(String[] args) { // 1、获取帖子 List<Shuoshuo> shuoshuoList = getShuoshuoList(); // 2、创建线程池对象,分别获取评论和点赞信息 ExecutorService commentPool = Executors.newSingleThreadExecutor(); ExecutorService likePool = Executors.newSingleThreadExecutor(); // 3、任务列表,将启动的线程存储进任务列表中,在最后统一获取数据 List<Future> commentFutureList = new ArrayList<>(); List<Future> likeFutureList = new ArrayList<>(); try { // 4、循环说说,获取评论和点赞信息,这里是耗时操作,此处开启对应的线程,同时获取数据,可以提升性能 for (Shuoshuo shuoshuo : shuoshuoList) { // 5、查看评论列表,此处只启动并执行线程任务,由于get会阻塞,此处不通过get方法获取执行结果, Future<List<Comment>> commentFuture = commentPool.submit(() -> { List<Comment> comment = getComment(shuoshuo.getId()); return comment; }); // 将future存储进任务列表中 commentFutureList.add(commentFuture); // 6、查看点赞列表 Future<List<ShuoshuoLike>> likeFuture = likePool.submit(() -> { List<ShuoshuoLike> likeList = getLike(shuoshuo.getId()); return likeList; }); likeFutureList.add(likeFuture); } // 7、循环说说列表,从任务列表中获取执行结果 for (int i = 0; i < shuoshuoList.size(); i ) { // List为有序集合,不需担心数据错位,或者可以使用Map集合根据说说id来存储对应的评论和点赞信息 Shuoshuo shuoshuo = shuoshuoList.get(i); shuoshuo.setComments((List<Comment>) commentFutureList.get(i).get()); shuoshuo.setShuoshuoLikes((List<ShuoshuoLike>) likeFutureList.get(i).get()); } }catch (ExecutionException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { // 8、关闭线程池 commentPool.shutdown(); likePool.shutdown(); } // 9、遍历说说列表 shuoshuoList.forEach(System.out::println); } }

运行结果:

callable接口图解(别只会背面试题了)(11)

总结

临近年关,公司业务也在结尾,加班加点继续码字,接下来会更新线程池相关技术点,觉得不错点点赞,关注,支持一下哦!

,