JUC高并发-Callable接口

  1. 1. Runnable接口与Callable接口区别:
  2. 2. 1.Callable接口
  3. 3. 2.Future接口
  4. 4. 3.FutureTask类
  5. 5. 代码实现demo

创建线程的方式有多种:

  1. 进程Thread类
  2. 实现Runnable接口
  3. Callable接口
  4. 线程池方式

问题:

​ 很多时候我们让多线程去帮我们处理事情,是需要拿到返回值的,有了异常也可以处理,比如某系统,一个页面展示3个块,而每个块展示的信息从后端获取的接口都不一样,那么是让前端调后端3次接口吗?
​ 这肯定不行,后端可以把3个块的信息,包装成一个接口,全部返回,那么问题来了,后端调用3个接口,比如第一个接口需要1秒,第二个需要2秒,第三个需要3秒,那么包装的这个接口响应时间最少6秒,怎么解决这个问题呢?

解决方案:

​ 可以用多线程来帮我们解决。
​ 启动3个线程,每个线程去调用一个接口,那么3个线程一共执行完的时间就是最慢的那个线程的执行时间,这样接口的响应时间就变成了3秒,一下节省了一半的时间。

Runnable接口与Callable接口区别:

1、Callable规定的方法是call(), Runnable规定的方法是run()

2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得

3、call方法可以抛出异常,run方法不可以

4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

1.Callable接口

​ 我们先回顾一下java.lang.Runnable接口,就声明了run(),其返回值为void,当然就无法获取结果了。

1
2
3
public interface Runnable {
public abstract void run();
}

而Callable的接口定义如下

1
2
3
public interface Callable<V> { 
V call() throws Exception;
}

该接口声明了一个名称为call()的方法,同时这个方法可以有返回值V,也可以抛出异常。

2.Future接口

​ Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:

1
2
3
4
5
6
7
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

方法解析:

  1. V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
  2. V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
  3. boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
  4. boolean isCanceller() :如果任务完成前被取消,则返回true。
  5. boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。

通过方法分析我们也知道实际上Future提供了3种功能

(1)能够中断执行中的任务

(2)判断任务是否执行完成

(3)获取任务执行完成后额结果。

​ 但是我们必须明白Future只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场啦。

3.FutureTask类

FutureTask类:

1
public class FutureTask<V> implements RunnableFuture<V> {

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

1
2
3
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}

​ 可以看出FutureTasks 是 RunnableFuture接口的实现类,RunnableFuture继承了Runnable接口和Future接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

FutureTask提供了2个构造器:

1
2
3
4
5
6
7
8
9
10
11
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}

事实上,FutureTask是Future接口的一个唯一实现类。

代码实现demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void callableTest() {
Map<String, Object> returnMap = new HashMap<>();
Callable<List<Film>> callable = ()-> {
return filmService.findAll()
.stream()
.filter(film -> "内地".equals(film.getRegion()))
.collect(Collectors.toList()); //去service 查询数据返回
};

Callable<List<User>> callable2 = ()-> userService.findAll(); //去service 查询数据返回2


Callable<List<Activity>> listCallable = () -> activityService.findAll();

FutureTask<List<Film>> futureTask = new FutureTask<>(callable);
FutureTask<List<User>> futureTask2= new FutureTask<>(callable2);
FutureTask<List<Activity>> listFutureTask = new FutureTask<>(listCallable);
Thread t1 = new Thread(futureTask); //声明线程
Thread t2 = new Thread(futureTask2);
Thread t3 = new Thread(listFutureTask);
t1.start(); //开始线程
t2.start();
t3.start();
try {
List<Film> list1 = futureTask.get(); //获取线程异步的执行结果
List<User> list2 = futureTask2.get();
List<Activity> list3 = listFutureTask.get();
returnMap.put("list1",list1); //将结果放入map中返回
returnMap.put("list2",list2);
returnMap.put("list3",list3);
System.out.println(returnMap);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
t1.interrupt(); //关闭线程
t2.interrupt();
t3.interrupt();
}
//返回 returnMap

}

​ 那如果2个线程一起执行,futureTask执行完了,futureTask2没执行完会不会有问题,是不是futureTask1就拿不到结果了?
​ 答案当然是不会,futureTask.get()方法会保证线程在执行完之前是阻塞的。

具体用在一个service 查询多个集合 返回 开启多个线程 节省查询时间