【Java編程的邏輯】線程池

基本介紹

線程池,顧名思義,就是一個線程的池子,裏有若干線程,它們的目的就是執行提交給線程池的任務,執行完一個任務後不會退出,而是繼續等待或執行新的任務。

線程池的優點:

  • 可以重用線程,避免線程創建的開銷
  • 任務過多時,通過排序避免創建過多線程,減少系統資源消耗和競爭,確任務有序完成

ThreadPoolExecutor

Java併發包中線程池的實現類是ThreadPollExecutor,它繼承了AbstractExecutorService,AbstractExecutorService實現了ExecutorService

我們先看看ThreadPoolExecutor的主要構造方法

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

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

我們先談談這些參數

線程池大小

  • corePoolSize
  • maximumPoolSize
  • keepAliveTime 和 unit

corePollSize表示線程池中的核心線程個數,並不是一開始就創建這麼多線程,默認情況下,剛創建一個線程池後並不會創建任何線程。線程個數小於等於corePoolSize時,我們稱這些線程爲核心線程

maximumPoolSize表示線程池中的最多線程數,線程的個數會動態變化,但都不會創建比這個值大的線程個數

一般情況下,新任務到來的時候,如果當前線程個數小於corePoolSize,就會創建一個新線程來執行該任務,即使其他線程現在也是空閒的。如果線程個數大於等於corePoolSize,那就不會立即創建新線程,而是先嚐試排隊,如果隊列滿了或其他原因不能立即入隊,它就不會排隊,而是檢查線程個數是否達到了maximumPoolSize,如果沒有,就會創建線程。

keepAliveTime表示當線程池中線程個數大於corePoolSize時額外空閒線程的存活時間。也就是說,一個非核心線程,在空閒等待新任務時,會有一個最長等待時間(keepAliveTime),如果到了時間還沒有新任務,就會被終止。如果該值爲0,則表示所有線程都不會超時終止。

默認情況下:核心線程不會預先創建,只有當有任務時纔會創建;核心線程不會因爲空閒而被終止。
不過ThreadPoolExecutor可以改變這個默認行爲

// 預先創建所有的核心線程
public int prestartAllCoreThreads();
// 創建一個核心線程,如果所有核心線程都已創建,返回false
public boolean prestartCoreThread();
// 如果設置爲true,則keepAliveTime參數也適用於核心線程
public void allowCoreThreadTimeout(boolean value);

隊列

ThreadPoolExecutor要求隊列類型是阻塞隊列BlockingQueue。
在介紹併發容器的時候瞭解過幾種阻塞隊列:

  • LinkedBlockingQueue:基於鏈表的阻塞隊列,可以指定最大長度,但默認是無界的
  • ArrayBlockingQueue:基於數組的有界阻塞隊列
  • PriorityBlockingQueue:基於堆的無界阻塞隊列
  • SynchronousQueue:沒有實際存儲空間的同步阻塞隊列

拒絕策略

如果隊列有界,且maximumPoolSize有限,則當隊列排滿,線程個數也達到了maximumPoolSize時,新任務來了,就會觸發線程池的任務拒絕策略
ThreadPoolExecutor實現了4種處理方法:
1. ThreadPoolExecutor.AbortPolicy:這是默認的方式,拋出異常RejectedExecutionException
2. ThreadPoolExecutor.DiscardPolicy:忽略新任務,不拋出異常,也不執行
3. ThreadPoolExecutor.DiscardOldestPolicy:將等待時間最長的任務扔掉,然後排隊
4. ThreadPoolExecutor.CallerRunsPolicy:在任務提交者線程中執行任務,而不是交給線程池中的線程執行

它們都是ThreadPoolExecutor的內部類,實現了RejectedExecutionHandler接口

public interface RejectedExecutionHandler { 
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

線程工廠

還有一個參數:ThreadFactory。它是一個接口

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

這個接口根據Runnable創建一個Thread。
ThreadPoolExecutor的默認實現是Executors類中的靜態內部類DefaultThreadFacotry,主要就是創建一個線程,給線程設置名字,設置daemon屬性爲false,設置線程優先級爲標準默認優先級,線程名字格式爲:pool-<線程池編號>-thread-<線程編號>

static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    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;
    }
}

工廠類Executors

類Executors提供了一些靜態工程方法,可以方便地創建一些預配置的線程池

newSingleThreadExecutor

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

只使用一個線程,使用無界的LinkedBlockingQueue,線程創建後不會超時終止,該線程順序執行所有任務。該線程適用於需要確保所有任務被順序執行的場合

newFixedThreadPool

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

使用固定數目的n個線程,使用無界LinkedBlockingQueue,線程創建後不會超時終止,由於是無界隊列,如果排隊任務過多,會消耗過多的內存。

newCachedThreadPool

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

corePoolSize爲0,maximumPoolSize爲Integer.MAX_VALUE,keepAliveTime是60秒,隊列爲SynchronousQueue。
當新任務來的時候,如果正好有空閒線程在等待任務,則其中一個空閒線程接受該任務,否則就總是創建一個新的線程,創建的總線程個數不受限制,對於空閒線程,如果60秒內沒有新任務就終止

線程池的死鎖

提交給線程池的任務,如果任務之間有依賴,可能會出現死鎖。

如果任務A提交給了一個單線程線程池,在它的執行過程中,它給同樣的任務執行服務提交了一個任務B,但需要等待任務B返回結果,這樣就會出現死鎖。

處理辦法:使用newCachedThreadPool創建線程池,讓線程數不受限制;使用SynchronousQueue作爲等待隊列,對於SynchronousQueue來說,入隊成功就意味着已有線程接受處理,如果入隊失敗,可以創建新的線程

擴展

Java線程池線程複用的祕密

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