【Java多線程-3】Future與FutureTask


前文中我們講述了創建線程的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表示是否允許取消正在執行卻沒有執行完畢的任務:

    1. 如果設置true,則表示可以取消正在執行過程中的任務。
    2. 如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false;
    3. 如果任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;
    4. 如果任務還沒有執行,則無論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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章