Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 並發二三事之Java線程池

Android 並發二三事之Java線程池

編輯:關於Android編程

最近在項目中接觸到了很多有關於多線程方面的東西,並且剛好前段時間看了Java並發編程實戰那本說,
所以想將自己所了解到的,以及實際中碰到的問題總結一下。

打算首先介紹一下,Java多線程相關的基礎,例如Thread,Runnable。雖然這個極其的基礎,但是我覺得任何東西都
繞不過基礎的知識。重點會在介紹線程池,包括線程池相關類的層級結構、相關參數等。
以及在Android中有那些多線程表現形式,例如:AsyncTask,HandlerThread,IntentService, AsycTaskLoader等。
之後希望能簡單的表述一下多線程與Future, TimeUnit , CountDownLatch 等等相關類的關系,以及如何在子線程中如何開啟子線程去請求數據,子線程與主線程如何交互數據等。
還有就是介紹多線程相關概念,鎖, 可重入鎖, 死鎖, 如何排查死鎖, 線程發布, 競態條件, 數據競爭 等等

應該會分幾篇博客來總結相關的知識。當然其中肯定會有一些錯誤之處,歡迎留言指出。我會及時更正。

以上相關所有知識點,有許多借鑒自其他的大神的博客,由於看了許多,沒辦法一一列舉,在此表示感謝。許多概念相關的知識來自於《Java並發編程實戰》那本書。

一、Thread 繼承Thread類,重寫run方法。

我覺得Thread應該是被最經常用到的。當只需要一個簡單的線程,不需要管理線程時直接:

new Thread(){
            @Override
            public void run() {
        //請求網絡等
            }
    }.start();

一個匿名內部類就寫完了,相信很多人都寫過這樣的代碼。需要注意的是,如果在Activity中寫這樣的代碼很容易造成內存洩露。
即當Activity結束時,線程的工作還沒有做完,這就會導致該Activity不會被回收。可以考慮用靜態內部類的形式代替上面的寫法。

二、 Runnable 實現Runnable接口重寫run()方法

public class DisableRunnable implements Runnable{

        @Override
        public void run() {
            //請求網絡等
        }
    }

Runnable 是執行任務的單元,需要用Thread包裝一下才可以執行。
Runnable 接口在Android中的使用也非常的多.例如View.post(),Handler.post()等等。

每當我們看到這樣的代碼時:

new Thread(){runnable}.start();

當你考慮用更靈活的策略來執行任務時,可以考慮利用線程池代替Thread。

三、 線程池:

下面主要說一下線程池相關的總結,
線程池結構:

最頂層接口 Executor

public interface Executor {

    void execute(Runnable command);

}

ExecutorService接口 繼承Executor 定義了一些有關於生命周期的方法。

public interface ExecutorService extends Executor {

    void shutdown();

    List shutdownNow();

    boolean isShutdown();

    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

     Future submit(Callable task);

     Future submit(Runnable task, T result);

    Future submit(Runnable task);

     List> invokeAll(Collection> tasks)
        throws InterruptedException;

     List> invokeAll(Collection> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

     T invokeAny(Collection> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ThreadPoolExecutor 間接實現了ExecutorService 是真正做事的線程池。最常見的幾種線程池都是它的實現。

ThreadPoolExecutor 常用的構造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              RejectedExecutionHandler handler){}

相關參數解釋:
corePoolSize 核心線程池大小
maximumPoolSize 最大線程池大小
keepAliveTime 線程池中超過corePoolSize數目的空閒線程最大存活時間;可以allowCoreThreadTimeOut(true)使得核心線程有效時間
TimeUnit keepAliveTime時間單位
workQueue 阻塞任務隊列
RejectedExecutionHandler 飽和策略 當提交任務數超過maxmumPoolSize+workQueue之和時,任務會交給RejectedExecutionHandler來處理

1.當線程池小於corePoolSize時,新提交任務將創建一個新線程執行任務,即使此時線程池中存在空閒線程。
2.當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行
3.當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會創建新線程執行任務
4.當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
5.當線程池中超過corePoolSize線程,空閒時間達到keepAliveTime時,關閉空閒線程

注意:
這裡maximumPoolSize 指的是corePoolSize + 由於隊列已滿並且maximumPoolSize>corePoolSize時,為執行任務創建的線程數。
所以第4條應該說成,當workQueue已滿,並且線程池內的線程數已經達到了maximumPoolSize最大線程數。這時,再次提交任務會交給RejectedExecutionHandler 飽和策略去處理。

四、這裡BlockingQueue
可以為:SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue 等等。
我們需要先了解這幾個隊列,當清楚這些隊列的性質後,再去看那幾個常用的線程池,將會非常容易理解。

SynchronousQueue
SynchronousQueue 首先它是無界的,也就是說它存儲的能力是沒有限制的。
但是它每一個put操作必須等待一個take操作。也就是說在某一次添加元素後,必須要等其他線程將它取走,否則不能添加。

LinkedBlockingQueue
LinkedBlockingQueue 無界隊列,也就是說使用LinkedBlockingQueue時,當所有corePoolSize都在忙時,新任務都會在LinkedBlockingQueue中等待。
因為LinkedBlockingQueue是無界的(最大長度為Integer.MAX_VALUE,這裡認為無界)。這個時候maximumPoolSize其實就無效了,因為只有當LinkedBlockingQueue已滿時,才會創建新的線程執行任務。而這裡LinkedBlockingQueue永遠都不會滿。所以maximumPoolSize也就無效了,也就是說利用這個隊列構造的線程池,永遠也不會創建新的線程。

ArrayBlockingQueue
ArrayBlockingQueue 有界隊列 可以設置隊列的大小,能夠有效的防止資源消耗。但是者也就導致這種情況控制起來會相對復雜,JDK建議的幾種常見的線程池都沒有使用這個隊列。

PriorityBlockingQueue 優先級隊列

Executors 線程池工具類,用於生產線程池。

五、幾類比較常見的線程池:

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

解釋:構建一個固定數目的線程池。corePoolSize 與 maximumPoolSize 數值相等。當線程池中沒有空閒線程時,利用 LinkedBlockingQueue
保存阻塞任務,因為之前已經說過了 LinkedBlockingQueue 是無界的,也就是說這個隊列永遠不會滿,那maximumPoolSize 其實就沒有任何意義。

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

解釋:構建一個單線程(只有一個線程,單不一定是同一個線程)的線程池。corePoolSize 與 maximumPoolSize 均為1 .利用無界的LinkedBlockingQueue 作為保存任務的隊列,這樣就保證無論提交多少個人物,線程池只能有一個線程在運行,其余的任務均保存在 LinkedBlockingQueue 隊列中。

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

解釋:構建一個具有緩存功能的線程池,corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE 。隊列用的是SynchronousQueue。每加入一個任務,線程池便會構建一個線程將它取出執行(如果有空閒的線程,會利用空閒的線程,而不會創建新的線程),這樣,加入多少個任務便會創建相同個數的線程。所以這個線程池的 maximumPoolSize 為Integer.MAX_VALUE。這就保證了線程池的最大線程數是無界的,理論上線程池可以有任意個線程。當線程執行完任務後,超過60秒的空閒時間即被回收銷毀。

六、飽和策略:

RejectedExecutionHandler 參數用來表示飽和策略。即表示當有界隊列已滿,並且當前線程池中的線程數已達到 maximumPoolSize ,這時再提交任務,會交給RejectedExecutionHandler 來處理。

注意:這裡一定是有界隊列,因為無界隊列我們認為是永遠也無法填滿的(SynchronousQueue直接由生產者提交給工作線程),那麼也就永不到飽和策略了。

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

}

RejectedExecutionHandler 本身是一個接口。在Executors中提供了幾個實現類。

1)AbortPolicy:中止,executor拋出未檢查RejectedExecutionException,調用者捕獲這個異常,然後自己編寫能滿足自己需求的處理代碼。

2)DiscardRunsPolicy:遺棄最舊的,選擇丟棄的任務,是本應接下來就執行的任務。

3)DiscardPolicy:遺棄會默認放棄最新提交的任務(這個任務不能進入隊列等待執行時)

4)CallerRunsPolicy:調用者運行,既不會丟棄哪個任務,也不會拋出任何異常,把一些任務推回到調用者那裡,以此減緩新任務流。它不會在池線程中執行最新提交的任務,但它會在一個調用了execute的線程中執行。

七、線程工廠:
線程池另外還有一個參數便是:ThreadFactory。之前在介紹常用的構造方法沒有說它的原因是,一般用不到。其他構造方法中都給出了默認實現:DefaultThreadFactory。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

public interface ThreadFactory {

    Thread newThread(Runnable r);

}

ThreadFactory 在 Executors 中也提供了幾個實現類,一般博主所接觸過的都是 DefaultThreadFactory 。當然我們可以自己定制,
用於添加一些Log等等。之前推薦的幾種線程池都有 ThreadFactory 作為參數的方法。

八、常見的線程池的使用:

自定義線程池:

private final static ExecutorService executorService = new ThreadPoolExecutor(
            5,
            10,
            30L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue(),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    //DO nothing
                }
            });

這裡我們用的是自定義的線程池。我們定義了一個核心線程池為5的線程池,線程池內最大的線程池數為10。

當然由於我們這裡定義的緩存隊列為LinkedBlockingQueue, 沒有制定隊列大小,那麼其默認無Integer.MAX_VALUE。
也就是無限大,所以最大線程數沒有用處。

飽和策略也是自定的,這裡當達到執行飽和策略時,什麼都不做,直接丟棄。

public void request() {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //請求網絡等。
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }


利用Executors 提供的線程池:

public void request() {

        ExecutorService executor = Executors.newFixedThreadPool(5);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //請求網絡等。
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }

OK, 線程池相關就介紹到這裡,下一篇會介紹Future,FutureTask, Callable 等等相關知識。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved