Java線程池

Java線程池

1. 簡介

系統啓動一個新線程的成本是比較高的,由於它涉及與操做系統的交互,這個時候使用線程池能夠提高性能,尤爲是須要建立大量聲明週期很短暫的線程時。Java中的線程池是運用場景最多的併發框架。java

線程池相似於數據庫鏈接池,在系統啓動的時候即建立大量空閒的線程,能夠將一個線程任務提交給線程池執行,當任務執行完後,線程不會死亡,而是再次返回線程池中成爲空閒狀態。數據庫

使用線程池的好處

  • 下降資源消耗:重複利用下降建立和銷燬線程的消耗;
  • 提升響應速度:任務來了能夠理解執行,沒必要等待線程建立;
  • 提升線程的可管理性:使用線程池能夠統一的分配、調優和監控。

2. 使用Executors工廠類來產生線程

Executors工廠類主要有4種方式生產線程池:小程序

1.newCachedThreadPool() :建立一個具備緩存功能的線程池,線程池無邊界,適用於執行不少短時間異步任務的小程序,或者負載較輕的服務器。數組

2. newFixedThreadPool(int nThreads):建立一個固定線程數的線程池,適用於爲了知足資源管理的要求而須要限制線程數量的場景,好比負載較重的服務器。緩存

3. newSingleThreadExecutor():建立一個只有單線程的線程池,適用於須要保證順序地執行各個任務,而且在任意時間點不會有多個線程是活動的西場景。服務器

4. newScheduledThreadPool(int corePoolSize):建立一個具備指定線程數的線程池,並能夠在指定延遲後執行線程任務,適用於多個後臺線程執行週期任務,同時爲了知足資源管理須要限制線程數量的場景。併發

實例代碼

package com.wangjun.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/*
 * 演示線程池的使用
 */
public class ThreadPoolTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        
        //生產普通的線程池
        ExecutorService threadPool = Executors.newFixedThreadPool(6);
        // 第一種執行線程的方式
        threadPool.submit(new MyThread()); 
        // 第二種執行線程的方式,有返回值
        Future<String> result = threadPool.submit(new MyThread(), "返回值1"); 
        System.out.println(result.get());
        // 第三種執行線程的方式,傳入Callable對象,有返回值
        Future<String> result2 = threadPool.submit(new MyThread2()); 
        System.out.println(result2.get());
        
        // 關閉線程池,再也不接受新的任務,會將以前全部提交的任務執行完成,全部任務完成後,全部的線程死亡
        // 調用shutdownNow能夠立馬中止全部線程
        threadPool.shutdown();
        System.out.println("-----");
        // 生產能夠延遲執行的線程池
        ScheduledExecutorService threadPool2 = Executors.newScheduledThreadPool(6);
        // 延遲1秒執行
        threadPool2.schedule(new MyThread(), 1, TimeUnit.SECONDS);
        // 延遲2秒後執行,每一秒循環執行一次
        // 是以上一個任務開始的時間計時,period時間過去後,檢測上一個任務是否執行完畢,若是上一個任務執行完畢,
        // 則當前任務當即執行,若是上一個任務沒有執行完畢,則須要等上一個任務執行完畢後當即執行。
        threadPool2.scheduleAtFixedRate(new MyThread(), 2, 1, TimeUnit.SECONDS);
        // 是以上一個任務結束時開始計時,period時間過去後,當即執行。
        // 兩個方法以不一樣的時間點做爲參考
        threadPool2.scheduleWithFixedDelay(new MyThread(), 2, 1, TimeUnit.SECONDS);
        
        Thread.sleep(3000);
        threadPool2.shutdown();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("線程:" + Thread.currentThread().getName());
        }
    }
    
    static class MyThread2 implements Callable<String>{
        @Override
        public String call() throws Exception {
            System.out.println("線程:" + Thread.currentThread().getName());
            return "返回值2";
        }
    }

}

3. 線程池類ThreadPoolExector

上面使用Executors工廠類生產線程池,其實大部分返回的就是ThreadPoolExector的實例。ThreadPoolExector纔是真正的線程池。框架

3.1 線程池的幾個關鍵屬性

  1. 核心線程數量corePoolSize:核心線程數,指保留的線程池大小(不超過maximumPoolSize值時,線程池中最多有corePoolSize 個線程工做)。
  2. 最大線程數量maximumPoolSize:指的是線程池的最大大小,使用使用了無界隊列,那這個參數就沒什麼用。
  3. 線程結束的超時時間keepAliveTime:當一個線程不工做時,過keepAliveTime時間將中止該線程(該線程是多餘的,指大於corePoolSize的那些線程)。
  4. 存聽任務的隊列workQueue:存放須要被線程池執行的線程隊列。
  5. 飽和策略handler:加任務失敗後如何處理該任務。一般是AbortPolicy,表示沒法處理新任務時拋出異常。

3.2 線程池執行策略

線程池執行execute()方法的過程如圖:異步

圖片描述

  1. 線程池剛建立時,裏面沒有一個線程。任務隊列是做爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會立刻執行它們。
  2. 當調用 execute() 方法添加一個任務時,線程池會作以下判斷:

​ a. 若是正在運行的線程數量小於 corePoolSize,則建立線程執行任務(注意,這一步須要獲取全局鎖);ide

​ b. 若是正在運行的線程數量大於或等於 corePoolSize而且隊列沒有滿,那麼將這個任務放入隊列。

​ c. 若是這時候隊列滿了,並且正在運行的線程數量小於 maximumPoolSize,那麼仍是要建立線程運行這個任務;

​ d. 若是隊列滿了,並且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會交給配置的執行策略處理(調用RejectedExecutionHandler.rejectedExecution方法),好比拋出異常,提示沒法加入新線程。

  1. 當一個線程完成任務時,它會從隊列中取下一個任務來執行。
  2. 當一個線程無事可作,超過必定的時間(keepAliveTime)時,線程池會判斷,若是當前運行 的線程數大於 corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize 的大小。

3.3 線程池中阻塞隊列的種類

  1. ArrayBlockingQueue:居於數組結構的有界阻塞隊列,按照先進先出FIFO原則對元素進行排序;
  2. LinkedBlockingQueue:基於鏈表結構的阻塞隊列,按照FIFO排序原色,吞吐量大於ArrayBlockingQueue,靜態工廠方法Executors.newFixedThreadPool()就使用了這個隊列;
  3. SynchronousQueue:不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於LinkedBlockingQueue,靜態工程方法Executors.newCachedThreadPool()就使用了這個隊列;
  4. PriorityBlockingQueue:具備優先級的無限阻塞隊列。

3.4 飽和策略

通常狀況下,線程池採用的是AbortPolicy,表示沒法處理新任務時拋出異常。JDK1.5中java線程池框架提供了4種飽和策略:

  • AbortPolicy:直接拋出異常;
  • CallerRunsPolicy:使用調用者所在線程來運行任務;
  • DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務;
  • DiscardPolicy:不處理,丟棄掉。

也能夠根據場景來實現RejectedExecutionHandler接口來自定義策略。

3.5 向線程池提交任務

  • execute:用於提交不須要返回值的任務,所以沒法判斷任務是否被線程執行成功;
  • submit:用於提交須要有返回值的任務,返回一個Future對象,經過get()獲取返回值,會阻塞當前線程直到任務完成。

3.6 關閉線程池

經過調用線程池的shutdownshutdownNow方法來關閉線程池,他們的原理是遍歷線程池中的工做線程,而後逐個調用線程的interrupt方法來中斷線程,因此沒法影響中斷的任務可能永遠沒法終止。

這兩個方法的區別是shutdownNow首先將線程池的狀態設置爲STOP,而後嘗試中止全部的正在執行或暫停任務的線程,並返回等待任務的列表,而shutdown只是將線程池的狀態設置爲SHUTDOWN狀態,而後中斷全部沒有正在執行任務的線程。

只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true,當全部的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。一般調用shutdown來關閉線程池,若是任務不必定要執行完,則能夠調用shutdownNow方法。

4. Executor框架

JDK5以前,java的線程Thread既是工做單元,也是執行單元(start方法執行),JDK5開始,把工做單元與執行機制分離開來,工做單元包括Runnable和Callable,而執行機制由Executor框架提供。(注意這裏的Executor和Executors線程池工廠類不是一回事。)

線程池類ThreadPoolExecutor的繼承關係:

ThreadPoolExecutor -> AbstractExecutorService -> ExecutorService(接口) -> Executor(接口)。

能夠看到ThreadPoolExecutor就是Executor接口的一個實現方法。

4.1 Executor的兩層調度模型

傳統的java線程(java.lang.Thread)在JVM中被一對一映射爲本地操做系統線程,java線程啓動時會建立一個本地操做系統線程,當java線程終止時,這個操做系統線程也會被回收。

而在Executor框架中,將用戶任務映射爲固定數量的線程,在底層操做系統內核將這些線程映射到硬件處理器上,應用程序經過Executor框架控制上層調度,下層的調度由操做系統內核控制。

圖片描述

4.2 Executor框架的結構

Executor框架主要有3部分組成:

  1. 任務:包括被執行任務須要實現的接口:Runnable和Callable;
  2. 任務的執行:核心接口是Executor,繼承Executor的ExecutorService接口。ExecutorService接口有兩個核心實現類,ThreadPoolExecutor和ScheduledThreadPoolExecutor(繼承ThreadPoolExecutor,能夠延遲執行任務,比Timer更強大)。
  3. 異步計算的結果:包括接口Future和實現Future接口的FutureTask類。

來看一下Executor框架中主要類和接口的UML圖:

圖片描述

相關文章
相關標籤/搜索