治理线程的第二法宝

Future和Callable

Runnable的缺陷

不能返回一个返回值

不能抛出checked Exception异常

Callable接口

类似于Runnable,被其他线程执行的任务

实现call方法

有返回值

看源码

Future类

作用

Callable和Future的关系

可以用Future.get()来获取Callable接口返回的执行结果,可以通过Future.isDone()来判断任务是否已经执行完,以及取消这个任务,限时获取任务的结果等

在call()未执行完毕之前,调用get()的线程会阻塞,直到call()方法返回了结果后,此时Future.get()才会得到该结果,然后主线程才会切换到Runnable状态

Future是一个存储器,它存储了call()这个任务的结果,而这个任务的执行时间是无法提前确定的,因为这完全取决于call()方法执行的情况

Future的主要方法

一共5个

get()

获取结果,get()方法的行为取决于Callable任务的执行状态,只有以下这5种状况

  1. 任务正常完成:get()方法会立即返回结果
  2. 任务尚未完成(任务还没开始或进行中):get()将阻塞并直接完成任务
  3. 任务执行过程中抛出Exception:get()方法会抛出ExecutionException,这里的抛出异常,是call()执行时产生的那个异常,这个异常类型是java.util.concurrent.Exception,不论call()执行时抛出的异常类型是什么,最后get方法抛出的异常类型都是ExectionException
  4. 任务被取消:get()方法会抛出CancellationException
  5. 任务超时:get()方法有一个重载方法,是传入一个延时时间。如果时间到了还没有获得结果,get方法就会抛出TimeoutException

get(long timeout,TimeUnit unit)

有超时时间的获取

超时的需求很常见

get(long timeout,TimeUnit unit)方法时,如果call()在规定时间内完成了任务,那么就会正常获取到返回值,如果在指定时间内没有计算出结果,就会抛出TimeoutException

超时不获取则任务需要取消

cancel()

取消任务的执行

isDone()

判断线程是否执行完毕

isCancelled()

判断是否被取消

get()基本用法

线程池的submit方法返回Future对象

首先要给线程池提交任务,提交过后线程池会立即返回一个空的Future容器,当线程的任务执行完毕,也就是可以获取结果的时候,线程池便会把该结果填入之前的Future中,而不是创建一个新的Future,此时便可以从该Future中获得任务的执行结果

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
/**
* 描述: 演示一个Future的使用方法
*/
public class OneFuture {

public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
Future<Integer> future = service.submit(new CallableTask());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdown();
}

static class CallableTask implements Callable<Integer> {

@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return new Random().nextInt();
}
}

多个任务用Future数组来获取结果

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
/**
* 描述: 演示批量提交任务时,用List来批量接收结果
*/
public class MultiFutures {

public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(20);
ArrayList<Future> futures = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Future<Integer> future = service.submit(new CallableTask());
futures.add(future);
}
Thread.sleep(5000);
for (int i = 0; i < 20; i++) {
Future<Integer> future = futures.get(i);
try {
Integer integer = future.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

static class CallableTask implements Callable<Integer> {

@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return new Random().nextInt();
}
}
}

任务执行过程中抛出异常Exception和isDone

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
/**
* 描述: 演示get方法过程中抛出异常,for循环为了演示抛出Exception的时机:并不是说一产生异常就抛出,直到我们get执行时,才会抛出。
*/
public class GetException {

public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
Future<Integer> future = service.submit(new CallableTask());


try {
for (int i = 0; i < 5; i++) {
System.out.println(i);
Thread.sleep(500);
}
System.out.println(future.isDone());
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("InterruptedException异常");
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println("ExecutionException异常");
}
}


static class CallableTask implements Callable<Integer> {

@Override
public Integer call() throws Exception {
throw new IllegalArgumentException("Callable抛出异常");
}
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
0
1
2
3
4
true
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Callable抛出异常
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at future.GetException.main(GetException.java:26)
Caused by: java.lang.IllegalArgumentException: Callable抛出异常
ExecutionException异常

获取任务超时

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 描述: 演示get的超时方法,需要注意超时后处理,调用future.cancel()。
* 演示cancel传入true和false的区别,future.cancel()代表是否中断正在执行的任务。
*/
public class Timeout {

private static final Ad DEFAULT_AD = new Ad("无网络时候的默认广告");
private static final ExecutorService exec = Executors.newFixedThreadPool(10);

static class Ad {

String name;

public Ad(String name) {
this.name = name;
}

@Override
public String toString() {
return "Ad{" +
"name='" + name + '\'' +
'}';
}
}


static class FetchAdTask implements Callable<Ad> {

@Override
public Ad call() throws Exception {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("sleep期间被中断了");
return new Ad("被中断时候的默认广告");
}
return new Ad("旅游订票哪家强?找某程");
}
}


public void printAd() {
Future<Ad> f = exec.submit(new FetchAdTask());
Ad ad;
try {
ad = f.get(2000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
ad = new Ad("被中断时候的默认广告");
} catch (ExecutionException e) {
ad = new Ad("异常时候的默认广告");
} catch (TimeoutException e) {
ad = new Ad("超时时候的默认广告");
System.out.println("超时,未获取到广告");
boolean cancel = f.cancel(true);
System.out.println("cancel的结果:" + cancel);
}
exec.shutdown();
System.out.println(ad);
}

public static void main(String[] args) {
Timeout timeout = new Timeout();
timeout.printAd();
}
}

cancel()方法

取消任务的执行

  1. 如果这个任务还没有开始执行,任务会被正常取消,以后也不会被执行,方法返回true
  2. 如果任务已完成或者已取消,那么cancel()方法会执行失败,方法返回false
  3. 如果这个任务已经开始执行了,那么这个取消方法将不会直接取消该任务,而是会根据填的参数mayInterruptIfRunning做判断

Future.cancel(true)适用于

  1. 任务能够处理interrupt

Future.cancel(false)仅用于避免启动尚未启动的任务,适用于

  1. 未能处理interrupt的任务
  2. 不清楚任务是否支持取消
  3. 需要等待已经开始的任务执行完成

用FutureTasK来创建Future

用FutureTask来获取Future和任务的结果

FutureTask是一种包装器,可以把Callable转化成Future和Runnable,它同时实现二者的接口

image-20200227085312718

所以它即可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

把Callable实例当做参数,生成FutureTask的对象,然后把这个对象当做一个Runnable对象,用线程池或另起一个线程去执行这个Runnable对象,最后通过FutureTask获取刚才执行的结果

注意点

当for循环批量获Future的结果时,容易发生一部分线程很慢的情况,get()方法调用时应该使用timeout限制

image-20200227090342711

Future的声明周期不能后退,生命周期只能前进,不能后退

文章目录
  1. 1. 治理线程的第二法宝
    1. 1.1. Runnable的缺陷
    2. 1.2. Callable接口
    3. 1.3. Future类
      1. 1.3.1. 作用
      2. 1.3.2. Callable和Future的关系
      3. 1.3.3. Future的主要方法
      4. 1.3.4. get()基本用法
      5. 1.3.5. 多个任务用Future数组来获取结果
      6. 1.3.6. 任务执行过程中抛出异常Exception和isDone
      7. 1.3.7. 获取任务超时
      8. 1.3.8. cancel()方法
      9. 1.3.9. 用FutureTasK来创建Future
      10. 1.3.10. 注意点
|