前文中我們講述了創建線程的2種方式:直接繼承Thread和實現Runnable接口,但這兩種方式在執行完任務之後都無法獲取執行結果。
自從Java 5開始,JDK提供了Callable和Future,解決了上述問題,通過它們可以在任務執行完畢之後得到任務執行結果。
1 Future
1.1 Future簡介
Future類位於java.util.concurrent包下,它是一個接口:
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;
}
在Future接口中聲明瞭5個方法:
-
cancel:取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務:
- 如果設置true,則表示可以取消正在執行過程中的任務。
- 如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false;
- 如果任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;
- 如果任務還沒有執行,則無論mayInterruptIfRunning爲true還是false,肯定返回true。
-
isCancelled:方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。
-
isDone:判斷任務是否已經完成,已完成則返回true;
-
get():獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;
-
get(long timeout, TimeUnit unit):用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。
1.2 Future使用示例
設想,有這樣一個場景,同時啓動3個線程分別執行一個任務,線程1耗時8s,線程2耗時7s,線程3耗時6s。用Future去接收線程執行結果,並手動維護一個List放置所有Future,代碼如下:
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import java.util.concurrent.*;
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/7 20:54
* @description
* @modify
*/
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(3);
class Task implements Callable<String> {
private int time;
public Task(int time) {
this.time = time;
}
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name + "啓動:" + new Date());
TimeUnit.SECONDS.sleep(time);
return name;
}
}
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Future<String> future = executor.submit(new Task(8 - i));
results.add(future);
}
for (int i = 0; i < 3; i++) {
System.out.println(results.get(i).get() + "完成:" + new Date());
}
System.out.println("全部線程執行完畢");
executor.shutdownNow();
}
}
輸出結果:
pool-1-thread-1啓動:Fri Nov 08 13:42:08 CST 2019
pool-1-thread-3啓動:Fri Nov 08 13:42:08 CST 2019
pool-1-thread-2啓動:Fri Nov 08 13:42:08 CST 2019
pool-1-thread-1完成:Fri Nov 08 13:42:16 CST 2019
pool-1-thread-2完成:Fri Nov 08 13:42:16 CST 2019
pool-1-thread-3完成:Fri Nov 08 13:42:16 CST 2019
全部線程執行完畢
可以看到,get方法具有阻塞性,線程1 的結果未返回前,其他已經完成的線程任務結果也無法獲取。下面對上面代碼進行改進:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Date;
import java.util.concurrent.*;
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/7 20:54
* @description
* @modify
*/
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(3);
class Task implements Callable<String> {
private int time;
public Task(int time) {
this.time = time;
}
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name + "啓動:" + new Date());
TimeUnit.SECONDS.sleep(time);
return name;
}
}
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Future<String> future = executor.submit(new Task(8 - i));
results.add(future);
}
boolean flag = true;
while (flag) {
for (Iterator<Future<String>> iter = results.iterator(); iter.hasNext(); ) {
Future<String> future = iter.next();
if (future.isDone()) {
System.out.println(future.get() + "完成:" + new Date());
iter.remove();
}
}
if (results.size() == 0) {
flag = false;
}
}
System.out.println("全部線程執行完畢");
executor.shutdownNow();
}
}
輸出結果:
pool-1-thread-2啓動:Fri Nov 08 14:12:43 CST 2019
pool-1-thread-1啓動:Fri Nov 08 14:12:43 CST 2019
pool-1-thread-3啓動:Fri Nov 08 14:12:43 CST 2019
pool-1-thread-3完成:Fri Nov 08 14:12:49 CST 2019
pool-1-thread-2完成:Fri Nov 08 14:12:50 CST 2019
pool-1-thread-1完成:Fri Nov 08 14:12:51 CST 2019
全部線程執行完畢
可以看到,一旦某個線程任務執行結束,其結果能被立即獲取到,但代價是程序在不停地循環查詢線程任務 isDone 的結果,對cpu消耗比較大。因此,使用Future解決多任務結果,並不是最優的效果。
FutureTask正是爲此而存在
2 FutureTask
2.1 FutureTask簡介
FutureTask類實現了RunnableFuture接口:
public class FutureTask<V> implements RunnableFuture<V>
RunnableFuture接口又繼承了Runable和Future
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可見,FutureTask既可以作爲Runnable被線程執行,又可以作爲Future得到Callable的返回值。FutureTask類圖如下:
下面我們再來看看 FutureTask 工具類。前面我們提到的 Future 是一個接口,而 FutureTask 是一個工具類,這個工具類有兩個構造函數:
FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);
2.2 FutureTask使用示例
使用Callable+FutureTask獲取執行結果:
import java.util.concurrent.*;
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/8 14:17
* @description
* @modify
*/
public class FutureTaskTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask("hello,", "world-");
//將任務放進FutureTask裏
FutureTask<Object> futureTask = new FutureTask<>(myTask);
//採用thread來開啓多線程
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyTask implements Callable<Object> {
private String param1;
private String param2;
//構造函數,用來向task中傳遞任務的參數
public MyTask(String param1, String param2) {
this.param1 = param1;
this.param2 = param2;
}
//任務執行的動作
@Override
public String call() {
for (int i = 0; i < 5; i++) {
System.out.println(param1 + param2 + i);
}
return "運行完成!";
}
}
輸出結果:
hello,world-0
hello,world-1
hello,world-2
hello,world-3
hello,world-4
運行完成!
也可以使用線程池:
import java.util.concurrent.*;
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/8 14:17
* @description
* @modify
*/
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創建 FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(() -> 1 + 2);
// 創建線程池
ExecutorService executor = Executors.newCachedThreadPool();
// 提交 FutureTask
executor.submit(futureTask);
// 獲取計算結果
Integer result = futureTask.get();
System.out.println(result);
executor.shutdown();
}
}
接下來,我們使用FutureTask來實現Future多線程獲取任務結果的場景:
import java.util.Date;
import java.util.concurrent.*;
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/8 14:17
* @description
* @modify
*/
public class FutureTaskTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
Callable<String> callable = new Task(8 - i);
MyFutureTask task = new MyFutureTask(callable);
executor.submit(task);
}
executor.shutdown();
}
}
class MyFutureTask extends FutureTask<String> {
public MyFutureTask(Callable<String> callable) {
super(callable);
}
@Override
protected void done() {
try {
System.out.println(get() + "完成:" + new Date());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Task implements Callable<String> {
private int time;
public Task(int time) {
this.time = time;
}
@Override
public String call() throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println(name + "啓動:" + new Date());
TimeUnit.SECONDS.sleep(time);
return name;
}
}
輸出結果:
pool-1-thread-1啓動:Fri Nov 08 17:35:26 CST 2019
pool-1-thread-3啓動:Fri Nov 08 17:35:26 CST 2019
pool-1-thread-2啓動:Fri Nov 08 17:35:26 CST 2019
pool-1-thread-3完成:Fri Nov 08 17:35:32 CST 2019
pool-1-thread-2完成:Fri Nov 08 17:35:33 CST 2019
pool-1-thread-1完成:Fri Nov 08 17:35:34 CST 2019