Thread->線程池

前言Callable與Future


在介紹線程池前,我們先介紹下Callable與Future因爲等會封裝異步任務會用到.而異步任務Runnable相信都在熟悉不過了,Callable與Runnable類似,但Callable有返回值.

public interface Callable<V> {

    V call() throws Exception;
}

類型參數就是返回值類型,比如Callable<Integer>表示一個返回值類型爲Integer的Callable.

至於異步任務Callable的執行結果會通過一個Future對象返回,Future接口具體有如下方法.

public interface Future<V> {
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
}
  • get()方法獲取Callable執行結果,如果還未執行完將會阻塞直到可以獲得結果,get(long timeout, TimeUnit unit)調用超時會拋出TimeoutException異常,如果運行該計算的線程被中斷了,兩個方法都將拋出InterruptedException.如果執行已經完成,那麼get方法立即返回.

  • 如果callable還在執行,isDone()方法返回false,如果完成了返回true.

  • 可以用cancel取消計算,如果計算還沒開始,它將被取消不在開始,如果計算已經運行中,那麼mayInterruptIfRunning爲true,它就被中斷.

可以說Future裏面不僅帶着執行結果,並且如果你想中斷當前計算也可以通過它.

使用的時候我們需要用FutureTask包裝一下,FutureTask可以將Callable轉換成Future和Runnable,它同時實現了二者的接口,下面我們來看一下如何使用

public class MyClass {

    public static void main(String[] args){

        FutureTask<Integer> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        try {
            task.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    static class MyCallable implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            return 100;
        }
    }
}

使用非常簡單這裏就不在過多贅言.

Executors線程池


創建一個新的線程是有一定代價的,如果程序創建了大量生命週期很短的線程,就應該使用線程池.

執行器Executors有很多靜態工廠方法可以構建線程池,具體如下

方法 描述
newCachedThreadPool 必要時創建新線程,空閒線程會被保留60秒
newFixedThreadPool 創建一個定長線程池,空閒線程會被一直保留,可控制線程最大併發數,超出的線程會在隊列中等待
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序執行
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行.代替Timer
newSingleThreadScheduledeExecutor 用於預定執行的單線程池

線程池

這裏我們分兩類介紹這些線程池,先看前三個.

  • newCachedThreadPool 方法構造的線程池,對於每個任務,如果有空閒線程可用,立即讓它執行,如果沒有空閒線程則創建新的線程.

  • newFixedThreadPool 方法構造一個具有固定大小的線程池,如果提交的任務數多於空閒的線程數,那麼把得不到服務的任務放置在隊列中.

  • newSingleThreadExecutor 是一個大小爲1的線程池,由一個線程執行提交的任務.

這三個返回實現了ExecutorService接口的ThreadPoolExecutor類的對象.

我們可以通過ExecutorService接口的submit與execute方法執行異步任務.

void execute(Runnable command);

Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);

execute比較簡單,這裏我們說下submit方法.

  • 第一個submit方法返回一個Future<?>,可以使用這個對象調用isDone,cancel,或者isCancel.但是get方法在完成的時候只是返回null

  • 第二個submit方法在執行完成後返回一個指定的result對象.

  • 第三個submit提交一個Callable,並且返回對應的Future.

順帶說下它們的區別

  1. 接收的參數不同

  2. submit有返回值,而execute沒有

  3. submit方便Exception處理,而execute處理異常比較麻煩.

當用完一個線程池的時候,調用shutdown,會使該線程池關閉,被關閉的線程池不在接受新的任務.當所有任務執行完了,線程池關閉.另一種是調用shutdownNow,該線程池取消還未開始的所有任務並試圖中斷還在運行的線程.

說了這麼多來個例子看看

public static void main(String[] args){

    ExecutorService threadPool = Executors.newCachedThreadPool();//創建一個線程池
    Future<Integer> future = threadPool.submit(new MyCallable(1));//提交一個任務
    try {
        System.out.println(future.get());//獲得執行結果
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    threadPool.shutdown();//關閉線程池
}

static class MyCallable implements Callable<Integer> {
    private int i;

    public MyCallable(int i) {
        this.i = i;
    }

    @Override
    public Integer call() throws Exception {
        Thread.sleep(i * 1000);
        return i;
    }
}

下面我們總結下使用線程池該做的

  1. 調用Executors類中的靜態方法獲取線程池

  2. 調用submit提交Runnable或Callable

  3. 如果想取消一個任務,或者想得到執行的結果那麼需要保存好Future對象

  4. 當不在提交任何任務的時候,調用shutdown關閉線程池.

預定任務

接下來看另外兩個線程池,Executors的newScheduledThreadPool和newSingleThreadScheduledeExecutor兩個方法返回的實現了ScheduledExecutorService接口的對象,ScheduledExecutorService接口具有預定執行和重複執行的方法,可以預定執行Runnable或者Callable在初始延遲後運行一次,也可以週期性的執行一個Runnable.

ScheduledExecutorService常用方法如下

  • public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

    在預定時間後執行任務

  • public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);

    在初始化延遲結束後,週期性的執行任務,週期執行間隔period.不受任務執行時間影響.

  • public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);

    在初始化延遲結束後,週期性的執行給定任務,受任務執行時間影響,當第一次執行完後延遲delay後再執行第二次.

下面一個簡單的例子,每隔兩秒執行一次的定時任務.

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override public void run() {
        //do something
    }
}, 0, 2000, TimeUnit.MILLISECONDS);

執行一組任務

前面我們已經介紹瞭如何使用線程池執行單個任務了,但有的時候需要執行一組任務,控制相關任務,這裏我們介紹另外兩組api.

<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

invokeAny方法提交一個Callable對象集合到線程池,並返回某個已經完成了的任務結果,無法知道究竟是哪個任務的結果.適用於比如要計算一個結果但是有很多種方式,但只要有一種方式計算出結果我們就不在需要計算了這種情況.

invokeAll方法提交一個Callable對象集合到線程池,返回一個Future對象列表,代表所有任務的結果.

到這裏線程池基礎就介紹完了,歡迎吹水.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章