java線程池復習

在操作系統中,線程是操作系統調度的最小單位,同時線程又是一種受限的系統資源,即線程不可能無限地產生,并且線程的創建和銷毀都會有相應的開銷。所以就有了線程池的引入,它可以避免因為頻繁創建和銷毀線程所帶來的系統開銷。Android中的線程來源于java,主要是通過Executor來派生特定的線程池。

優點:
(1).重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷。
(2).能有效地控制線程池的最大并發數,避免大量的線程之間因互相搶占系統資源而導致的阻塞現象。
(3).能夠對線程進行簡單的管理,并提供定時執行以及指定間隔循環執行等功能。

Executor接口的真正實現為ThreadPoolExecutor,通過配置它的參數可以創建各種線程池。在介紹線程池的種類之前,先需要介紹下該類中每個參數的作用。

ThreadPoolExecutor


它有幾種構造函數提供我們實例化,看它參數最多的構造方法

//七個參數的構造函數
public ThreadPoolExecutor(int corePoolSize,
                     int maximumPoolSize,
                     long keepAliveTime,
                     TimeUnit unit,
                     BlockingQueue<Runnable> workQueue,
                     ThreadFactory threadFactory,
                     RejectedExecutionHandler handler)
  • 1.核心線程 corePoolSize

線程池新建線程時,如果當前線程總數小于核心線程 corePoolSize,則新建核心線程,如果超過corePoolSize,則新建非核心線程。

核心線程默認是一直在存活在線程池中,即使處于閑置狀態。

如果指定 ThreadPoolExecutorallowCoreThreadTimeOut 這個屬性為 true,那么核心線程如果處于閑置狀態的話,超過一定時間(keepAliveTime),就會被銷毀掉。

  • 2.線程總數 maximumPoolSize

線程中的最大線程數,等于核心線程加上非核心線程。

  • 3.超時時間 keepAliveTime

非核心線程超時時長,一個非核心線程的閑置時間超過這個線程設置的時長,就會被銷毀。同時如果設置allowCoreThreadTimeOut = true,則會作用于核心線程。

  • 4 . 時間單位

keepAliveTime 的時間單位 TimeUnit ,枚舉值,有以下幾種:

NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小時
DAYS : 天

  • 5 . 隊列 BlockingQueue

它是一個接口,代表一個任務隊列,維護著等待執行的runnable, 當所有的核心線程創建之后,就會將待執行的任務存入任務隊列,如果任務隊列滿了,則創建非核心線程,根據具體的實現,有不同的使用情形,下面是常用的任務隊列實現。

SynchronousQueue 任務隊列接受任務時,把任務直接提交給線程處理,如果所有線程都在工作,那就新創建一個線程來處理,所以此種任務隊列一般需要設置maximumPoolSizeInteger.MAX_VALUE,以防出現不能新建線程產生錯誤。

LinkedBlockingQueue 任務隊列接受任務時候,如果當前線程數小于核心線程數,則新建核心線程處理任務,如果當前線程數等于核心線程數,則進入隊列等待,由于這個任務隊列沒有最大值限制,則所有超過核心線程都會被添加到隊列中,這也就導致了maximumPoolSize 的設定失效,線程總數不會超過corePoolSize

ArrayBlockingQueue 限定任務隊列的長度,接受到任務時候,如果沒有達到corePoolSize 的值,則新建核心線程執行任務,如果已經達到了,則入隊列等待,如果隊列已經滿了,則新建非核心線程執行任務,如果線程總數達到了maximumPoolSize,則發生錯誤

DelayQueue 隊列內元素必須實現Delayed 接口,這就表示你傳進去的任務必須實現Delayed 接口,入隊列的元素,首先會先入隊列,只有達到了指定的延時時間,才會執行任務

  • 6 . ThreadFactory

創建線程的方式,Executors 有默認的ThreadFactory創建方式,一般沒有特別需求可以直接使用它的默認實現方式,提交的任務通過它創建線程

/**
     * The default thread factory.
     */
    private static class DefaultThreadFactory implements ThreadFactory {
...
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
  • 7 . RejectedExecutionHandler

拋出異常專用,也就是線程池的飽和策略ThreadPoolExecutor 實現了幾種常用異常

ThreadPoolExecutor.AbortPolicy:默認的策略,丟棄任務并拋出RejectedExecutionException異常,這種策略需要加try catch,否則程序會直接退出
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常,空方法。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)。
ThreadPoolExecutor.CallerRunsPolicy 在調用execute的線程里面執行此策略,會阻塞入口
用戶自定義飽和策略(最常用)
實現RejectedExecutionHandler,并自己定義策略模式

關于線程池詳細源碼分析以及使用過程,可以看這篇文章

常見的四種線程池


Executors默認給我們提供了幾種線程池,常用的有四種,都是通過ThreadPoolExecutor 直接或者間接實現的,調用者可以很方便的使用它

  • 1 . CachedThreadPool

創建方法:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

好處:

1.線程無限制
2.有空閑線程則復用空閑線程,若無空閑線程則新建線程
3.一定程序減少頻繁創建/銷毀線程,減少系統開銷

  • 2 .FixedThreadPool

創建方法:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

只有核心線程,線程總數是一定的

好處:

1.可控制線程最大并發數(同時執行的線程數)
2.超出的線程會在隊列中等待

  • 3 . ScheduledThreadPool

創建方法:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory);
}

好處:

1 . 這個線程池支持定時或者周期性任務

  • 4 . SingleThreadExecutor

創建方法:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

好處:

1.有且僅有一個工作線程執行任務
2.所有任務按照指定順序執行,即遵循隊列的入隊出隊規則

關于這幾種線程池的原理,可以看這篇文章

Android中線程池的使用


作為一名Android開發者,在平時我們開發中,使用的原生API或者第三方庫,都會或多或少使用線程池,下面舉出幾個我在平時開發中使用到線程池的地方。

  • 1 . EventBus中線程池的使用

熟悉EventBus源碼的人都會知道,它里面有三個Poster,不熟悉的看這里,它在EventBus中起到一個線程切換的作用,同時也提供一個線程去執行異步任務。其中它們的默認使用如下

private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

可以看見創建了一個CachedThreadPool,這個線程池都是非核心線程,可以在一定程度上減輕頻繁創建線程所帶來的線程開銷。

  • 2 . Okhttp中線程池的使用

Okhttp中有個分發器Dispatcher,在里面維護了一個executorService,它的創建如下:

public synchronized ExecutorService executorService() {
 if (executorService == null) {
   executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
       new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
 }
 return executorService;
}

它是完全自定義的ThreadPoolExecutor,可以看見它的核心線程為0,非核心線程設置為
Integer.MAX_VALUE 和和EventBus的線程數一樣

  • 3 . AsyncTask中線程池的使用

剛學Android的時候,這個AsyncTask 作為異步任務的API 用得還是比較廣的,它里面封裝了線程池和Handler,在異步任務和UI線程中切換非常方便,看下其線程池的使用

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
    }

它的核心線程是Math.max(2, Math.min(CPU_COUNT - 1, 4)), 也就是最少是兩個,CPU_COUNT 是CPU的數,總線程總數是CPU_COUNT * 2 + 1,而它的任務隊列是new LinkedBlockingQueue<Runnable>(128),這樣它的任務隊列的最大長度是128。這樣就達到了控制線程的并發數,當線程達到最大核心線程數時候,其它任務就會被放到任務隊列中。

還有很多地方也用到了線程池,就不一一舉例了。

參考文檔


1 . 詳解 Java 線程池
2 . Android開發者探索
3 . Android開發者進階
4 . Java線程池(ThreadPoolExecutor)原理分析與使用
5 . Java線程Executor框架詳解與使用

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 為什么使用線程池 當我們在使用線程時,如果每次需要一個線程時都去創建一個線程,這樣實現起來很簡單,但是會有一個問題...
    閩越布衣閱讀 4,303評論 10 45
  • 一.Java中的ThreadPoolExecutor類 java.uitl.concurrent.ThreadPo...
    誰在烽煙彼岸閱讀 657評論 0 0
  • 我和老公認識三四年以來,幾乎每年夏天都會來一趟秦皇島。七八月的石家莊就跟大悶鍋一樣,一出門人很容易變成清蒸魚或水煮...
    sara王閱讀 615評論 0 0
  • 曾記否,“三鹿奶粉事件”的報道席卷了各大媒體,一經披露,舉國震驚!無辜兒童由于食用含有三聚氰胺而患腎結石,健康深受...
    管聯180閱讀 566評論 0 0
  • 有這樣一類人(一) 本文所述均是胡言亂語,不要對號入座,以免內傷。 有這樣一類人,他們身無長技,...
    好物閱讀 516評論 0 0