Java多線程併發04——合理使用線程池

在此以前,咱們已經瞭解了關於線程的基本知識,今天將爲各位帶來,線程池這一技術。關注個人公衆號「Java面典」瞭解更多 Java 相關知識點。html

爲何使用線程池?線程池作的工做主要是控制運行的線程的數量。java

線程池的種類

Java 中經常使用的線程池主要有四種:newCachedThreadPoolnewFixedThreadPoolnewScheduledThreadPoolnewSingleThreadExecutor緩存

newCachedThreadPool

做用:建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程(緩存中已有 60 秒鐘未被使用的線程),若無可回收,則新建線程。多線程

特色:線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。併發

實現框架

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    final int index = i;
    try {
        Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    cachedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(index);
        }
    });
}

newFixedThreadPool

做用:建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。ide

特色性能

  1. 若是在關閉前的執行期間因爲失敗而致使任何線程終止,那麼一個新線程將代替它執行後續的任務(若是須要);
  2. 在某個線程被顯式地關閉以前,池中的線程將一直存在。

定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()。this

實現線程

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}

newScheduledThreadPool

做用:建立一個定長線程池,支持定時及週期性任務執行。

實現

ScheduledExecutorService scheduledThreadPoo l= Executors.newScheduledThreadPool(3); 

scheduledThreadPool.schedule(newRunnable() { 
    @Override 
    public void run() {
        System.out.println("延遲三秒");
    }
}, 3, TimeUnit.SECONDS);

scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ 
    @Override 
    public void run() {
        System.out.println("延遲 1 秒後每三秒執行一次");
    }
}, 1, 3, TimeUnit.SECONDS);

newSingleThreadExecutor

做用:建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。

特色:這個線程池能夠在線程死後(或發生異常時)從新啓動一個線程來替代原來的線程繼續執行下去。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {

        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}

線程池原理

做用線程複用控制最大併發數管理線程。線程池作的工做主要是控制運行的線程的數量,處理過程當中將任務放入隊列,而後在線程建立後啓動這些任務,若是線程數量超過了最大數量超出數量的線程排隊等候,等其它線程執行完畢,再從隊列中取出任務來執行。

線程複用

每個 Thread 的類都有一個 start() 方法。 當調用 start 啓動線程時 Java 虛擬機會調用該類的 run() 方法。 那麼該類的 run() 方法中就是調用了 Runnable 對象的 run() 方法。 咱們能夠繼承重寫Thread 類,在其 start () 方法中添加不斷循環調用傳遞過來的 Runnable 對象。 這就是線程池的實現原理。循環方法中不斷獲取 Runnable 是用 Queue 實現的,在獲取下一個 Runnable 以前能夠是阻塞的。

線程池的組成

通常的線程池主要分爲如下 4 個組成部分:

  1. 線程池管理器:用於建立並管理線程池;
  2. 工做線程:線程池中的線程;
  3. 任務接口:每一個任務必須實現的接口,用於工做線程調度其運行;
  4. 任務隊列:用於存放待處理的任務,提供一種緩衝機制。

Java 中的線程池是經過 Executor 框架實現的,該框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 這幾個類。ThreadPoolExecutor 的構造方法以下:

/**
* @param corePoolSize 指定線程池中的線程數量
* @param maximumPoolSize 指定線程池中的最大線程數量
* @param keepAliveTime 當前線程池數量超過 corePoolSize 時,多餘的空閒線程的存活時間
* @param unit keepAliveTime 的單位
* @param workQueue 任務隊列,被提交但還沒有被執行的任務
*/
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    // threadFactory 線程工廠,用於建立線程,通常用默認的便可
    // handler 拒絕策略,當任務太多來不及處理,如何拒絕任務
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}

拒絕策略

做用:線程池中的線程已經用完了,沒法繼續爲新任務服務,同時,等待隊列也已經排滿了,再也塞不下新任務了。這時候咱們就須要拒絕策略機制合理的處理這個問題。

JDK 內置的拒絕策略以下

  1. AbortPolicy : 直接拋出異常,阻止系統正常運行;
  2. CallerRunsPolicy : 只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣作不會真的丟棄任務,可是,任務提交線程的性能極有可能會急劇降低;
  3. DiscardOldestPolicy : 丟棄最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務;
  4. DiscardPolicy : 該策略默默地丟棄沒法處理的任務,不予任何處理。若是容許任務丟失,這是最好的一種方案。

以上內置拒絕策略均實現了 RejectedExecutionHandler 接口,若以上策略仍沒法知足實際須要,徹底能夠本身擴展 RejectedExecutionHandler 接口。

Java 線程池工做過程

  1. 線程池剛建立時,裏面沒有一個線程。任務隊列是做爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會立刻執行它們;
  2. 當調用 execute() 方法添加一個任務時,線程池會作以下判斷:
    a). 若是正在運行的線程數量小於 corePoolSize,那麼立刻建立線程運行這個任務;
    b). 若是正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列;
    c). 若是這時候隊列滿了,並且正在運行的線程數量小於 maximumPoolSize,那麼仍是要
    建立非核心線程馬上運行這個任務;
    d). 若是隊列滿了,並且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常 RejectExecutionException。
  3. 當一個線程完成任務時,它會從隊列中取下一個任務來執行;
  4. 當一個線程無事可作,超過必定的時間(keepAliveTime)時,線程池會判斷,若是當前運行的線程數大於 corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize 的大小。

多線程與併發系列推薦

Java多線程併發03——什麼是線程上下文,線程是如何調度的

Java多線程併發02——線程的生命週期與經常使用方法,你都掌握了嗎

Java多線程併發01——線程的建立與終止,你會幾種方式

相關文章
相關標籤/搜索