什么是線程池?


什么是線程池?

線程池就是創建若干個可執行的線程放入一個池(容器)中,有任務需要處理時,會提交到線程池中的任務隊列,處理完之后線程並不會被銷毀,而是仍然在線程池中等待下一個任務。

 

為什么要使用線程池?

因為 Java 中創建一個線程,需要調用操作系統內核的 API,操作系統要為線程分配一系列的資源,成本很高,所以線程是一個重量級的對象,應該避免頻繁創建和銷毀。
使用線程池就能很好地避免頻繁創建和銷毀。

 

線程池是一種生產者——消費者模式

先看下一個簡單的 Java 線程池的代碼
 

package constxiong.concurrency.a010;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

/**
 * 簡單的線程池
 * @author ConstXiong
 */
public class ThreadPool {
    
    //阻塞隊列實現生產者-消費者
    BlockingQueue<Runnable> taskQueue;
    
    //工作線程集合
    List<Thread> threads = new ArrayList<Thread>();
    
    //線程池的構造方法
    ThreadPool(int poolSize, BlockingQueue<Runnable> taskQueue) {
        this.taskQueue = taskQueue;
        
        //啟動線程池對應 size 的工作線程
        for (int i = 0; i < poolSize; i++) {
            Thread t = new Thread(() -> {
                while (true) {
                    Runnable task;
                    try {
                        task = taskQueue.take();//獲取任務隊列中的下一個任務
                        task.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            threads.add(t);
        }
    }
    
    //提交執行任務
    void execute(Runnable task) {
        try {
            //把任務方法放到任務隊列
            taskQueue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

 

線程池的使用測試

package constxiong.concurrency.a010;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 測試線程池的使用
 * @author ConstXiong
 */
public class TestThreadPool {

    public static void main(String[] args) {
        // 創建有界阻塞任務隊列
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>(10);
        // 創建 3個 工作線程的線程池
        ThreadPool tp = new ThreadPool(3, taskQueue);
        
        //提交 10 個任務
        for (int i = 1; i <= 10; i++) {
            final int j = i;
            tp.execute(() -> {
                System.out.println("執行任務" + j);
            });
        }
    }
    
}

 



打印結果

執行任務1
執行任務2
執行任務3
執行任務6
執行任務5
執行任務4
執行任務8
執行任務7
執行任務10
執行任務9

 

這個線程池的代碼中

  • poolSize 是線程池工作線程的個數
  • BlockingQueue taskQueue 是用有界阻塞隊列存儲 Runnable 任務
  • execute(Runnable task) 提交任務
  • 線程池對象被創建,就自動啟動 poolSize 個工作線程
  • 工作線程一直從任務隊列 taskQueue 中取任務


線程池的原理就是這么簡單,但是 JDK 中的線程池的功能,要遠比這個強大的多。

 

JDK 中線程池的使用

JDK 中提供的最核心的線程池工具類 ThreadPoolExecutor,在 JDK 1.8 中這個類最復雜的構造方法有 7 個參數。

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

 

  • corePoolSize:線程池保有的最小線程數。
  • maximumPoolSize:線程池創建的最大線程數。
  • keepAliveTime:上面提到項目根據忙閑來增減人員,那在編程世界里,如何定義忙和閑呢?很簡單,一個線程如果在一段時間內,都沒有執行任務,說明很閑,keepAliveTime 和 unit 就是用來定義這個“一段時間”的參數。也就是說,如果一個線程空閑了keepAliveTime & unit這么久,而且線程池的線程數大於 corePoolSize ,那么這個空閑的線程就要被回收了。
  • unit:keepAliveTime 的時間單位
  • workQueue:任務隊列
  • threadFactory:線程工廠對象,可以自定義如何創建線程,如給線程指定name。
  • handler:自定義任務的拒絕策略。線程池中所有線程都在忙碌,且任務隊列已滿,線程池就會拒絕接收再提交的任務。handler 就是拒絕策略,包括 4 種(即RejectedExecutionHandler 接口的 4個實現類)。
    • AbortPolicy:默認的拒絕策略,throws RejectedExecutionException
    • CallerRunsPolicy:提交任務的線程自己去執行該任務
    • DiscardPolicy:直接丟棄任務,不拋出任何異常
    • DiscardOldestPolicy:丟棄最老的任務,加入新的任務


JDK 的並發工具包里還有一個靜態線程池工廠類 Executors,可以方便地創建線程池,但是由於 Executors 創建的線程池內部很多地方用到了無界任務隊列,在高並發場景下,無界任務隊列會接收過多的任務對象,導致 JVM 拋出OutOfMemoryError,整個 JVM 服務崩潰,影響嚴重。所以很多公司,尤其互聯網大廠已經不建議使用 Executors 去創建線程。

 

Executors 的簡介

雖然不建議使用,作為對 JDK 的學習,還是簡單介紹一下.

  1. newFixedThreadPool:創建定長線程池,每當提交一個任務就創建一個線程,直到達到線程池的最大數量,這時線程數量不再變化,當線程發生錯誤結束時,線程池會補充一個新的線程
  2. newCachedThreadPool:創建可緩存的線程池,如果線程池的容量超過了任務數,自動回收空閑線程,任務增加時可以自動添加新線程,線程池的容量不限制
  3. newScheduledThreadPool:創建定長線程池,可執行周期性的任務
  4. newSingleThreadExecutor:創建單線程的線程池,線程異常結束,會創建一個新的線程,能確保任務按提交順序執行
  5. newSingleThreadScheduledExecutor:創建單線程可執行周期性任務的線程池
  6. newWorkStealingPool:任務可竊取線程池,不保證執行順序,當有空閑線程時會從其他任務隊列竊取任務執行,適合任務耗時差異較大。

 

 

 來一道刷了進BAT的面試題?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM