線程池(ThreadPool)--解析

jdk1.5之前我們使用線程的時候就去創建一個線程,這樣實現起來的非常方便。

但是有一個問題就是,如果併發的線程數量很多,並且每個線程創建出來就執行一個任務就結束了,這樣頻繁的創建和銷燬線程會大大的降低系統的效率,因爲創建和銷燬線程需要時間。

jdk5之後加入了java.utilconcurrent包,這個包中主要介紹java中線程以及線程池的使用。爲我們在開發中處理線程的問題提供了非常大的幫助。

什麼是線程池?

用來管理線程的一個概念,使得線程可以複用,就是線程執行完成一個任務並不被銷燬,而是可以繼續執行其他的任務。java通過線程池來達到這樣的效果的。

線程池的有點

  1. 線程是稀缺資源,使用線程池可以減少創建和銷燬線程的次數,每個工作線程都可以重複使用
  2. 可以根據系統的承受能力,調整線程池中工作線程的數量,防止因爲消耗過多的內存導致服務器崩潰

ThreadPoolExecutor

在這裏插入圖片描述
java.util.concurrent.ThreadPoolExecutor是線程池中最核心的類。

構造方法

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    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);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

ThreadPoolExcutor類繼承了AbstrackExecutorService類,並提供了四個構造方法,事實上,前三個構造方法都是調用的第四個構造方法。

構造方法中各個參數的含義:

  • corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實現原理有非常大的關係。在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任何任務到來之前就創建corePoolSize個線程或者一個線程,默認情況下,在創建了線程池之後,線程池中的線程數爲0,當有任務來了之後,就會創建一個線程去執行任務,當線程池中的線程數達到corePoolSize(核心線程數)後,就會把到達的任務放到緩衝隊列中;
  • maximumPoolSize:線程池中最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程;
  • keepAliveTime:表示除了核心線程外的其他線程沒有任務執行時最多保持多長時間會終止。默認情況下,只有對那個線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean )方法,在線程池中的線程數不大於核心線程數 的時候,keepAliveTime參數也會起作用,直到線程池中的線程數爲0;
  • unit:參數keepAliveTime的時間單位,有7中取值,在TimeUnit類中有7中靜態屬性;

在這裏插入圖片描述

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒
  • workQueue:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,一般來說,這裏的阻塞隊列有以下幾種。
ArrayBlockQueue;
LinkedBlockingQueue;
SynchronousQueue;
  • threadFactory:線程工廠,主要用來創建線程;
  • handler:表示當拒絕處理任務時的策略,有一下四種取值;
ThreadPoolExecutor.AbortPolicy //丟棄任務並拋出RejectedExecutionException異常
ThreadPoolExecutor.DiscardPolicy//也是丟棄任務,但是不拋出異常--悄無聲息的丟棄
ThreadPoolExecutor.DiscardOldestPolicy//丟棄隊列最前面的任務,並執行當前任務
ThreadPoolExecutor.CallerRunsPolicy //直接調用executor來執行當前任務
public interface Executor {
    void execute(Runnable command);
}

在上面的類關係圖中可以看出,Executor是一個頂層接口,在它裏面只聲明瞭一個方法execute(Runnable),返回值爲void,參數類型爲Runnable類型,從字面意思可以理解就是用來執行傳進去的任務的。

interface ExecutrorService
在這裏插入圖片描述
然後ExecutorService接口繼承了Executor接口,並聲明瞭一些方法

抽象類AbstractExecutorService實現了ExecutorService接口,基本實現了ExecutorService中聲明的所有方法;

在這裏插入圖片描述

ThreadPoolExecutro類繼承了類AbstranctExecutorService
在ThreadPoolExecutor類中有幾個非常重要的方法:

execute()
submit()
shutdown()
shutdownNow()

execute()方法實際上是ExecutorService中聲明的方法,在ThreadPoolExecutor中進行了具體的實現,這個方法是ThreadPoolExecutro的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。

submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中並沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務的執行結果,去看submit()方法的實現,會發現它實際上還是調用execute()方法,只不過它利用了Future來獲取任務執行結果

shutdown()和shutdownNow()是用來關閉線程池的

核心成員變量
在這裏插入圖片描述

線程池的主要控制狀態時ctl,它是一個原子的整數,其包含兩個概念字段:

  • workerCount:有效的線程數量
  • runState:線程池的狀態
    爲了在一個整型值裏面包含着兩個字段,我們限制workerCount最多(2^29) -1

線程池的運行狀態

  • RUNNING:接受新的任務,並處理隊列中的任務
  • SHUTDOWN:不接受新的任務,繼續處理隊列中的任務
  • STOP:不接收新的任務,也不繼續處理隊列中的任務,並且中斷正在處理的任務
  • TIDYING:所有任務都結束了,workerCount是0,通過調用terminated()方法轉換狀態
  • TERMINATED:terminated()方法已經完成
    狀態之間的轉換
    RUNNING->SHUTDOWN
    調用shutdown()方法,或者隱式調用finalize()方法
    (RUNNING或者SHUTDOWN)->STOP
    調用shutdownNow()方法
    SHUTDOWN->TIDYING
    當隊列和池都是空的時候
    STOP->TIDYING
    當池是空的時候
    TIDYING->TERMINATED
    當terminated()方法調用完成時。

Integer.SIZE=31

Integer.SIZE - 3 = 29

所以,COUNT_BITS = 29

高3位存儲runState

線程池的執行流程

在這裏插入圖片描述

  • 由圖可知,任務進來的時候,首先判斷核心線程池是否已滿,如果已經滿了判斷核心線程是否有空閒,如果有空閒或者核心線程池不滿,就由核心線程先執行任務
  • 如果核心線程已滿,則判斷任務隊列是否有地方存放該任務,如果有就將任務保存在任務隊列中,等待執行
  • 如果等待隊列隊列也滿了,再判斷是否達到最大線程數,如果沒有達到就創建非核心線程執行任務,如果超出了,就調用handler實現拒絕策略。

submit()方法和execute()方法


public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }


public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

這兩個方法有什麼區別呢?
我們發現submit最終還是會調用execute方法,不同的是submit方法提供了一個Future來託管返回值的處理,當調用這個方法需要有返回值的時候,可以用這個方法,execute方法只能接收Runnable作爲參數,而submit方法還可以接收Callable。

public void execute(Runnable command) {
		//如果當前任務爲空,拋出異常
        if (command == null)
            throw new NullPointerException();
        
        int c = ctl.get();
        //wordCountOf獲取線程池的當前線程數,如果小於規定的核心線程數,則執行addWorker創建新線程執行任務並返回
        if (workerCountOf(c) < corePoolSize) {
        	//true是代表往核心線程池創建線程
        	//false是代表創建核心線程之外的工作線程
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果線程池處於RUNNING狀態,並且成功的把任務放入等待隊列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
        	//如果線程的狀態不是RUNNING,並嘗試從等待隊列中刪除任務成功
            if (! isRunning(recheck) && remove(command))
            	//則調用線程飽和策略處理任務
                reject(command);
            //如果處於running狀態,但是沒有線程,則創建線程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //再次嘗試創建核心線程池之外的線程並執行任務
        else if (!addWorker(command, false))
        	//如果
            reject(command);
    }

executor方法基本分三步

  • 開啓線程執行任務,直到達到最大核心線程數
  • 達到核心線程數時,將接受的新的任務放入工作隊列
  • 當工作隊列也滿之後,就開啓非核心線程,直到到達最大線程數
  • 以上條件都不滿足的時候,就執行默認的拒絕策略

addWorker方法源碼

private boolean addWorker(Runnable firstTask, boolean core) {
        retry: //循環標誌
        for (;;) { 死循環
            int c = ctl.get();//獲取狀態位
            int rs = runStateOf(c);//計算線程池的狀態

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;//這一段說的是線程池不能正常運行的情況:線程池狀態關閉、任務爲空、隊列爲空返回錯誤

            for (;;) {//死循環
                int wc = workerCountOf(c);//計算線程數
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;//如果線程數超出核心線程數,返回錯誤
                if (compareAndIncrementWorkerCount(c))//增加worker的數量
                    break retry;//回到進入該方法的循環狀態
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;//如果狀態發生改變,就回退
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;//線程是否開始運行
        boolean workerAdded = false;//worker是否添加成功
        Worker w = null;
        try {
            w = new Worker(firstTask);//封裝成worker
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;//加鎖
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());計算線程池狀態

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;//worker添加成功
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();//啓動剛剛添加的任務
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);//失敗後執行的操作
        }
        return workerStarted;
}

線程測試

package cn.tedu.ThreadPoolDemo1;

import java.util.concurrent.*;

public class ThreadPoolDemo1 {

    public static void main(String[] args) {

        LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(4,7,50,TimeUnit.SECONDS,queue);

        for(int i = 0;i<10;i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });

            System.out.println("線程池中活躍的線程數爲"+executor.getActiveCount());
            if(queue.size()>0){
                System.out.println("被阻塞的線程數爲"+queue.size());
            }
        }
    }
}
線程池中活躍的線程數爲1
線程池中活躍的線程數爲2
線程池中活躍的線程數爲3
線程池中活躍的線程數爲4
線程池中活躍的線程數爲4
被阻塞的線程數爲1
線程池中活躍的線程數爲4
被阻塞的線程數爲2
線程池中活躍的線程數爲4
被阻塞的線程數爲3
線程池中活躍的線程數爲5
被阻塞的線程數爲3
線程池中活躍的線程數爲6
被阻塞的線程數爲3
線程池中活躍的線程數爲7
被阻塞的線程數爲3

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task cn.tedu.ThreadPoolDemo1.ThreadPoolDemo1$1@330bedb4 rejected from java.util.concurrent.ThreadPoolExecutor@2503dbd3[Running, pool size = 7, active threads = 7, queued tasks = 3, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at cn.tedu.ThreadPoolDemo1.ThreadPoolDemo1.main(ThreadPoolDemo1.java:14)

可以看到創建了3個核心線程和4個非核心線程,當線程數超過了線程池可容納的最大數量。執行了拒絕策略Reject,說明隊列和線程池都滿了,線程池處於飽和狀態,另外一個原因是完成的線程沒有及時釋放而是進入了休眠狀態。

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