Java併發編程之Executor框架

1. 概述

java中實現多線程最直接、最基礎的做法是實現Runnable接口、繼承Thread類和實現Callable接口。對於每一個任務創建一個線程的方式,如果任務數量過多,過度消耗資源,會引起內存溢出的問題(Out of Memory)。

實際上,java線程池的引入也來源於生活中的實際例子。我們去火車站買票,如果每來一人購票,車站就開一窗口,那麼很快車站的資源就耗盡了,沒有場地,沒有資金,去一直開下去。假設車站在平時開設5個窗口,我們稱其爲核心(core)窗口,那麼國慶假期或春運期間,購票人數激增,車站會增加窗口,而受資源限制,車站可將窗口最多增至10個,如果超過10人以上來購票,就需要在窗口按照“先來後到(FIFO)”的方式進行排隊等候了。

java自JDK 1.5引入了線程池和Executor框架,至此,開發者就可以像“火車站購票”一樣,方便的使用線程池對多線程進行管理了。

2. Executor接口

來自JDK1.5的 java.util.concurrent 包。

void execute(Runnable command)

Executor是一個Functional interface,提供了一個接受Runnable命令的方法。

class ThreadPerTaskExecutor implements Executor {
   public void execute(Runnable r) {
     new Thread(r).start();
   }
 }

可以像上面的例子一樣使用Executor,但這不是重點,配合線程池才能發揮其作用。

public class ThreadPoolDemo {
    private static final int NTHREADS = 100;
    private static final Executor es = Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) {
        es.execute(() -> System.out.println("execute task"));
    }
}

Executors.newFixedThreadPool(NTHREADS)創建了一個有NTHREADS數量的線程池。當有新的任務提交時,如果線程池有空餘的線程,則立即執行,否則,新的任務會放入隊列等待。

3. Executor框架提供的線程池

3.1 ExecutorService接口

public interface ExecutorService extends Executor

Executors接口繼承了Executor接口,它提供了shutdown()方法,可以關閉線程池,不再接受新的任務。同時,Executors還增加了submit方法返回一個Future對象,來獲取異步任務返回的結果。

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

3.2 JDK提供的線程池

Executors類提供了一些工廠方法創建線程池:
public static ExecutorService newFixedThreadPool(int nThreads)
創建一個線程池,只有固定數量的線程。在任何時候,線程池中最多有nThreads個激活的線程,如果有額外的任務提交,而沒有多餘的線程可用時,額外的任務就需要在隊列中進行等待。如果有線程因爲異常退出,則會補充新的線程。
public static ExecutorService newSingleThreadExecutor()
創建只有一個線程的線程池。
public static ExecutorService newCachedThreadPool()
根據需要創建線程。如果線程池中有可複用的線程,就優先使用可複用的線程,否則添加新的線程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創建指定大小的線程池,任務可在指定的延遲時間後執行或週期性的執行。

3.3 自定義線程池

3.2節提到的jdk提供的創建線程池的方法,底層是通過實例化ThreadPoolExecutor去實現的。newFixedThreadPool創建固定數量的線程池,當任務過多時,會造成任務的堆積;newCachedThreadPool根據需要創建,又會造成資源浪費,甚至Out of Memory的問題。一般在項目中更多的是根據需要自定義線程池:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize - 線程池中始終保留的線程個數;
maximumPoolSize - 允許在線程池中存在的最大線程個數;
keepAliveTime - 當線程池中線程的數量超過corePoolSize時,多餘的空閒線程的存活時間;
unit - keepAliveTime的單位;
workQueue - 保存已提交但未執行的任務的隊列;
threadFactory - 線程工廠;
handler - 拒絕策略,當任務過多,來不及執行時的拒絕策略。

備註:ExecutorService 繼承了 Executor 接口,ThreadPoolExecutor 實現了 ExecutorService。

4. 一個自定義線程的例子

public class MyThreadPool {
    private static ThreadFactory myThreadPoolFactory =
            new ThreadFactoryBuilder()
                    .setNameFormat("MyThread ThreadFactory-pool")
                    .build();
    private static ThreadPoolExecutor EXECUTOR =
            new ThreadPoolExecutor(
                    12,
                    100,
                    50L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    myThreadPoolFactory,
                    new ThreadPoolExecutor.AbortPolicy());

    private static Future<Integer> fun(int x) {
        return EXECUTOR.submit(() -> x * 2 - 5);
    }

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 8, 9, 10};
        List<Future<Integer>> futures = Lists.newArrayList();
        Arrays.stream(arr).forEach(x -> futures.add(fun(x)));
        try {
            List<Integer> list = Lists.newArrayList();
            for(Future<Integer> f : futures) {
                list.add(f.get());
            }
            System.out.println(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. 超載拒絕策略

線程池提供了4種拒絕策略:
static class ThreadPoolExecutor.AbortPolicy
直接拋出一個RejectedExecutionException,阻止系統正常工作。
static class ThreadPoolExecutor.CallerRunsPolicy
只要線程池未關閉,該策略直接在調用者線程中運行被丟棄的任務。
static class ThreadPoolExecutor.DiscardOldestPolicy
該策略丟棄最老的任務,也就是丟棄即將被執行的任務,然後重新提交當前任務。這個策略更看重的是當前提交的任務。“只見新人笑,那關舊人哭。”
static class ThreadPoolExecutor.DiscardPolicy
這個策略則是直接把當前無法執行的任務丟棄了。

附錄:多線程中類和接口的繼承關係(來源於網絡)
繼承關係

todo
scheduleExecutorService的例子。

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