Wetts's blog

Stay Hungry, Stay Foolish.

0%

Java-多线程-Callable接口及Futrue接口详解.md

转自:https://www.cnblogs.com/guanbin-529/p/11784914.html

Callable接口

有两种创建线程的方法:一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即run() 完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口。

  • 为了实现 Runnable,需要实现不返回任何内容的 run() 方法,而对于 Callable,需要实现在完成时返回结果的 call() 方法。请注意,不能使用 Callable 创建线程,只能使用 Runnable 创建线程。
  • 另一个区别是 call() 方法可以引发异常,而 run() 则不能。
  • 为实现 Callable 而必须重写 call 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Java program to illustrate Callable 
// to return a random number
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class CallableExample implements Callable
{

public Object call() throws Exception
{
// Create random number generator
Random generator = new Random();

Integer randomNumber = generator.nextInt(5);

// To simulate a heavy computation,
// we delay the thread for some random time
Thread.sleep(randomNumber * 1000);

return randomNumber;
}
}

Futrue接口

当 call() 方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦 Callable 返回)。因此,Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,但是由于下面的示例使用了库中的具体实现,因此这里仅列出了重要的方法。

  • public boolean cancel(boolean mayInterrupt):用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true 时才会中断任务。
  • public Object get() 抛出 InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
  • public boolean isDone():如果任务完成,则返回 true,否则返回 false

可以看到 Callable 和 Future 做两件事:Callable 与 Runnable类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。实际上,future 也可以与 Runnable 一起使用。

要创建线程,需要 Runnable。为了获得结果,需要 future。

Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。
可以通过为其构造函数提供 Callable 来创建 FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建 Thread 对象。因此,间接地使用 Callable 创建线程。

使用Callable和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
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.example.thread.callable;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

/**
* @author: GuanBin
* @date: Created in 下午11:19 2019/10/31
*/
public class TestCallable implements Callable<Object> {

private int taskNum;

public TestCallable(int taskNum) {
this.taskNum = taskNum;
}

//1,2主要区别是创建线程的方式
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();

}

/**
* 使用Executors.newFixedThreadPool创建线程池
* @throws InterruptedException
* @throws ExecutionException
*/
private static void test1() throws InterruptedException, ExecutionException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize=5;
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new TestCallable(i);
// 执行任务并获取Future对象
Future f = pool.submit(c);
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString()); //OPTION + return 抛异常
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
}

/**
* 线程直接使用new Thread来创建
* @throws ExecutionException
* @throws InterruptedException
*/
private static void test2() throws ExecutionException, InterruptedException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize=5;
FutureTask[] randomNumberTasks = new FutureTask[5];
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < randomNumberTasks.length; i++) {
Callable c = new TestCallable(i);
// 执行任务并获取Future对象
randomNumberTasks[i]= new FutureTask(c);

Thread t = new Thread(randomNumberTasks[i]);
t.start();
}

// 获取所有并发任务的运行结果
for (Future f : randomNumberTasks) {
// 从Future对象上获取任务的返回值,并输
System.out.println(">>>" + f.get().toString()); //OPTION + return 抛异常
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");

}

/**
* call方法的实现,主要用于执行线程的具体实现,并返回结果
* @return
* @throws Exception
*/
@Override
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
----程序开始运行----
>>>0任务启动
>>>1任务启动
>>>2任务启动
>>>3任务启动
>>>4任务启动
>>>0任务终止
>>>0任务返回运行结果,当前任务时间【1002毫秒】
>>>1任务终止
>>>2任务终止
>>>4任务终止
>>>1任务返回运行结果,当前任务时间【1005毫秒】
>>>2任务返回运行结果,当前任务时间【1005毫秒】
>>>3任务终止
>>>3任务返回运行结果,当前任务时间【1005毫秒】
>>>4任务返回运行结果,当前任务时间【1005毫秒】
----程序结束运行----,程序运行时间【1007毫秒】

Process finished with exit code 0

使用 Callable 和 FutureTask 的完整示例

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
// Java program to illustrate Callable and FutureTask 
// for random number generation
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class CallableExample implements Callable
{

public Object call() throws Exception
{
Random generator = new Random();
Integer randomNumber = generator.nextInt(5);

Thread.sleep(randomNumber * 1000);

return randomNumber;
}

}

public class CallableFutureTest
{
public static void main(String[] args) throws Exception
{

// FutureTask is a concrete class that
// implements both Runnable and Future
FutureTask[] randomNumberTasks = new FutureTask[5];

for (int i = 0; i < 5; i++)
{
Callable callable = new CallableExample();

// Create the FutureTask with Callable
randomNumberTasks[i] = new FutureTask(callable);

// As it implements Runnable, create Thread
// with FutureTask
Thread t = new Thread(randomNumberTasks[i]);
t.start();
}

for (int i = 0; i < 5; i++)
{
// As it implements Future, we can call get()
System.out.println(randomNumberTasks[i].get());

// This method blocks till the result is obtained
// The get method can throw checked exceptions
// like when it is interrupted. This is the reason
// for adding the throws clause to main
}
}
}

启动线程后,与线程的所有交互都使用 FutureTask,因为它实现了 Future 接口。因此,不需要存储 Thread 对象。使用 FutureTask 对象,还可以取消任务,检查任务是否完成或尝试获取结果。

使用Runnable来获取返回结果的实现

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
// Java program to illustrate Runnable 
// for random number generation
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class RunnableExample implements Runnable
{
// Shared object to store result
private Object result = null;

public void run()
{
Random generator = new Random();
Integer randomNumber = generator.nextInt(5);

// As run cannot throw any Exception
try
{
Thread.sleep(randomNumber * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}

// Store the return value in result when done
result = randomNumber;

// Wake up threads blocked on the get() method
synchronized(this)
{
notifyAll();
}
}

public synchronized Object get()
throws InterruptedException
{
while (result == null)
wait();

return result;
}
}

// Code is almost same as the previous example with a
// few changes made to use Runnable instead of Callable
public class RunnableTest
{
public static void main(String[] args) throws Exception
{
RunnableExample[] randomNumberTasks = new RunnableExample[5];

for (int i = 0; i < 5; i++)
{
randomNumberTasks[i] = new RunnableExample();
Thread t = new Thread(randomNumberTasks[i]);
t.start();
}

for (int i = 0; i < 5; i++)
System.out.println(randomNumberTasks[i].get());
}
}