線程池基本

線程池基本


一、優勢

  • 避免頻繁的創建和銷燬線程
  • 提供運行效率
  • 合理設置線程池大小,避免因線程數超過硬件資源瓶頸帶來的問題

二、類型

  • Executors.newFixedThreadPool() 創建固定線程長度的線程池
  • Executors.newCachedThreadPool() 創建不限個數的線程池,空閒線程會在 60s 後被回收
  • Executors.newSingleThreadExecutor() 創建只有一個線程的線程池,只有一個工作線程在工作,FIFO
  • Executors.newScheduledThreadPool() 創建一個可以指定數量的線程池,但這個帶有延遲性和可以週期性執行任務的功能
  • Executors.newWorkStealingPool()

三、使用

public class Demo implements Runnable {
    @Override
    public void run() {
        System.err.println("哈哈哈哈");
    }

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        for (int i = 0 ; i<= 3; i++) {
            service.execute(new Demo());
        }
    }
}

四、ThreadPool 分析

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

我們可以發現,大部分的線程池的創建操作都是通過 new ThreadPollExecutor 進行創建的,我們找到它的構造函數

    public ThreadPoolExecutor(int corePoolSize, // 核心線程數
                              int maximumPoolSize, // 最大線程數
                              long keepAliveTime, // 線程的空閒時間,超過時間後將會被回收
                              TimeUnit unit, // 線程超時的時間單位
                              BlockingQueue<Runnable> workQueue, // 阻塞隊列
                              RejectedExecutionHandler handler) { // 拒絕策略
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
4.1 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • 在構建該線程池的時候,會傳遞一個線程數,這個傳遞過來的值,代表了核心線程數和最大線程數,通過使用 LinkedBlockingQueue 隊列來進行線程的存放,最大容量是 Integer.MAX_VALUE,相當於沒有上限
  • 執行流程
    • 首先判斷當前的工作線程是否少於核心線程數
    • 如果大於核心線程數,將其放入阻塞隊列中
    • 如果少於核心線程數,則直接創建 Worker 進行執行
    • 當線程執行完畢,從阻塞隊列中拉取,執行
  • 適用於負載比較大的場景,但爲了資源的合理利用,要合理的設置線程數
4.2 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • cachedThreadPool ,默認創建一個可緩存的線程池,keepAliveTime 設置爲 60,代表線程空閒 60S 之後,就會被回收
  • 執行流程
    • 沒有核心線程,會直接將任務放到 SynchronousQueue 中
    • 如果有空閒線程則直接運行,若沒有空閒線程,則新建一個
    • 執行完畢後的線程會繼續接任務,如果60S後還沒有接到任務,則會被直接回收
4.3 newSingleThreadExecutor

簡單的創建一個 先進先出的單線程的線程池

五、執行流程

當我們調用 execute

5.1 execute
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get(); 
        if (workerCountOf(c) < corePoolSize) {
            // 如果當前的工作線程小於核心線程,則直接創建線程執行
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) { // 核心線程池已滿,添加到隊列中
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command)) // 如果線程池已經停止運行,並將其移除隊列,則執行拒絕策略
                reject(command);
            else if (workerCountOf(recheck) == 0) // 隊列已滿,創建非核心線程
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) // 都滿,拒絕
            reject(command);
    }

ctl 的話可以說是一個標記爲

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1; // 右移29位-1表示最大線程容量

    // 高3位表示運行狀態,
    private static final int RUNNING    = -1 << COUNT_BITS; // 接收新任務,執行隊列中的任務
    private static final int SHUTDOWN   =  0 << COUNT_BITS; // 不接收新任務,執行隊列中的任務
    private static final int STOP       =  1 << COUNT_BITS; // 不接收新任務,停止執行隊列中的任務
    private static final int TIDYING    =  2 << COUNT_BITS; // 所有任務執行完畢,線程池工作線程數量爲0,等待調用 terminated()
    private static final int TERMINATED =  3 << COUNT_BITS; // terminated() 方法執行完畢

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

通過對 int 值進行位運算,讓高3位表示線程狀態,低29位表示線程數

5.2 addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 
            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)) // 進行計數,失敗則繼續進行
                    break retry;
                c = ctl.get();  // 檢查一下狀態,若狀態發生改變,則重新執行
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
	
        boolean workerStarted = false; // 是否成功啓動
        boolean workerAdded = false;  // 是否成功添加
        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 {
                    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); // 添加到工作隊列中,HashSet
                        int s = workers.size(); // 獲取當前隊列大小
                        if (s > largestPoolSize) // largestPoolSize 是出現過得最大數
                            largestPoolSize = s;
                        workerAdded = true; // 標記成功
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start(); // 啓動
                    workerStarted = true; // 標記啓動成功
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w); // 添加失敗
        }
        return workerStarted;
    }

主要實現兩個功能:

  • 添加工作線程數,計數
  • 創建工作線程,並調用 start 方法啓動
5.3 Worker
private final class Worker extends AbstractQueuedSynchronizer
        implements Runnable{
    
     final Thread thread; // 當前線程
        
     Runnable firstTask; // 首先要執行的任務
 
     volatile long completedTasks; // 計數
    
    Worker(Runnable firstTask) {
            setState(-1); // 初始狀態 -1,防止在調用 runWorker() 方法前被中斷
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
     }
    
    public void run() {
            runWorker(this);
        }
}

其實這裏面邏輯也比較簡單,Worker 類繼承了 AQS,實現了 Runnable ,最終的執行是調用 runWroker() 方法,

當新建一個 worker 的時候,首先設置一個標記爲 state = -1, 通過線程工廠創建出來線程,將最初要執行的task 設置爲當前 task

5.4 addWorkerFailed
   
private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                // 如果 worker 構建好了,就移除
                workers.remove(w);
            // 原子遞減
            decrementWorkerCount();
            // 嘗試結束線程池
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }
5.5 runWorker
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 解鎖爲了允許進行中斷,因爲 new Worker 默認的 state=-1,將 state 設置爲0,只有爲0才能進行中斷
        boolean completedAbruptly = true;
        try {
            // 如果 task 等於空,通過 getTask() 從阻塞隊列中取
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt(); // 中斷
                try {
                    beforeExecute(wt, task); // 空實現
                    Throwable thrown = null;
                    try {
                        task.run(); // 調用 run 方法,執行
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;// 設置爲 Null , 繼續從阻塞隊列中取值
                    w.completedTasks++; // 計數
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            // 將 worker 移除,計數遞減
            // 根據布爾值 allowCoreThreadTimeOut 來決定是否補充新的 Worker線程
            processWorkerExit(w, completedAbruptly);
        }
    }
5.6 getTask
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

             // timed 變量用於判斷是否需要進行超時控制。 
			// allowCoreThreadTimeOut 默認是 false,也就是核心線程不允許進行超時; 
        	// wc > corePoolSize,表示當前線程池中的線程數量大於核心線程數量; 
        	// 對於超過核心線程數量的這些線程,需要進行超時控制
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 如果需要控制超時,則通過 poll 進行獲取
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r; //如果拿到的任務不爲空,則直接返回給 worker 進行處理 
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

其實簡單來說,如果當前的工作線程大於核心線程數,並小於最大線程數,且阻塞隊列已經滿了,這時還是可以增加工作線程,但這時如果超時沒有獲取到任務,就代表阻塞隊列已經空了,也就代表可以清除核心線程之外的工作線程,當getTask 爲null的時候,會跳出循環,runWorker 方法執行完畢,由JVM對線程進行回收

六、拒絕策略

默認的拒絕策略爲 : AbortPolicy

  • AbortPolicy : 拋出異常

  • CallerRunsPolicy : 通過當前線程,直接運行

  • DiscardOldestPolicy :丟棄阻塞隊列中靠最前的任務,並執行當前任務

  • DiscardPolicy :不作爲

我們可以通過實現 RejectedExecutionHandler , 實現自己的拒絕策略

七、線程池的基本操作

通過 Executors 進行線程池的創建,因爲好多參數我們不需要去知道是什麼意思,所以就可能會導致出現各種各樣的問題,所以我們可以通過 new ThreadExecutor 的方式去進行創建

  • 當我們創建出來線程池的時候,裏面是沒有線程的,我們可以通過 prestartCoreThreadprestartAllCoreThread,一個初始化一個核心線程,一個初始化全部核心線程

  • 當我們要關閉線程池的時候,shutdownshutdownNow,一個調用之後,會執行完畢纔會關閉,一個會停止執行,清空緩存隊列,返回尚未執行完畢的任務

  • 我們可以通過 setCorePoolSize() , setMaximumPoolSize(),一個設置核心池大小,一個設置最大線程數

八、阻塞隊列

workQueue 中存放待執行的任務,類型爲 BlockingQueue,通常可取:

  • ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;

  • LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默 Integer.MA_VALUE;

  • SynchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個 線程來執行新來的任務。

九、execute 與 submit 的區別

  • execute

    • execute 接收 Runnable 類型參數
    • execute 執行報錯會拋出異常
    • execute 無返回值
  • submit

    • 可以接收 Runnable 和 Callable 兩種類型的參數
    • submit 執行報錯不會拋出異常,除非調用 Future.get
    • 若傳入的是 Callable 類型的參數,則可以獲得一個 Future 類型的返回值

十、Callable 和 Future

  • 實例

        @Override
        public String call() throws Exception {
            return "hello world";
        }
    
        public static void main(String[] args) throws ExecutionException,
                InterruptedException {
            Demo callableDemo = new Demo();
            FutureTask futureTask = new FutureTask(callableDemo);
            new Thread(futureTask).start();
            System.out.println(futureTask.get());
        }
    
        public FutureTask(Callable<V> callable) {
            if (callable == null)
                throw new NullPointerException();
            this.callable = callable;
            this.state = NEW;       // ensure visibility of callable
        }
    

RunnableFuture 是一個接口,實現了 Future 和 Runnable

Future 的話表示一個任務的生命週期,並提供相關的判斷,來查看線程執行的一些狀態

public interface Future<V> {
	// 取消
    boolean cancel(boolean mayInterruptIfRunning);
	// 判斷是否取消
    boolean isCancelled();
	// 判斷是否結束
    boolean isDone();
	// 獲取,如果當前線程還沒執行完畢,則等待執行完畢
    V get() throws InterruptedException, ExecutionException;
	// 有超時的獲取
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

FutureTask 可以說是 Future 和 Runnable 的結合,我們可以將 Runnable 看作是生產者,Future 當做是消費者,生產者通過 run 方法計算結果,消費者通過 get 獲取結果

Future 有一些狀態值

    private static final int NEW          = 0;// 新建狀態,表示這個 FutureTask 還沒有開始運行
    private static final int COMPLETING   = 1;// 完成狀態,表示 FutureTask 任務已經計算完成了
    private static final int NORMAL       = 2;// 正常執行完畢
    private static final int EXCEPTIONAL  = 3;// 執行完畢,有異常
    private static final int CANCELLED    = 4;// 執行完畢,被取消
    private static final int INTERRUPTING = 5;// 執行完畢,發起了中斷請求
    private static final int INTERRUPTED  = 6;// 執行完畢,已經完成中斷請求
  • run

    public void run() {
        	// 如果當前不是昔年狀態,且設置運行標識失敗,則直接 return
        	// 保證了只有一個線程可以執行
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable; // 設置 callable
                if (c != null && state == NEW) {
                    V result;
                    boolean ran;
                    try {
                        // 調用 callable.call()
                        result = c.call();
                        ran = true;
                    } catch (Throwable ex) {
                        result = null;
                        ran = false;
                        setException(ex); // 設置異常
                    }
                    if (ran)
                        // 結果封裝
                        set(result);
                }
            } finally {
                runner = null;
                int s = state;
                if (s >= INTERRUPTING)
                    handlePossibleCancellationInterrupt(s);
            }
        }
    
  • get

        public V get() throws InterruptedException, ExecutionException {
            int s = state;
            if (s <= COMPLETING)
                s = awaitDone(false, 0L);
            return report(s);
        }
    

    get方法就是阻塞獲取線程執行結果,這裏主要做了兩個事情

    1. 判斷當前的狀態,如果狀態小於等於 COMPLETING,表示 FutureTask 任務還沒有完結, 所以調用awaitDone方法,讓當前線程等待。
      1. report返回結果值或者拋出異常
  • awaitDone

    private int awaitDone(boolean timed, long nanos)
            throws InterruptedException {
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            WaitNode q = null;
            boolean queued = false; // 節點是否添加
            for (;;) {
                // 如果被中斷了,則直接移除,並拋出異常
                if (Thread.interrupted()) {
                    removeWaiter(q);
                    throw new InterruptedException();
                }
    
                int s = state;
                if (s > COMPLETING) { // 表示已經執行完畢
                    if (q != null)
                        q.thread = null; // 設置爲 Null
                    return s;
                }
                else if (s == COMPLETING) // cannot time out yet
                    Thread.yield();
                else if (q == null)
                    // 構建一個 waitNode ,加入到隊列中
                    q = new WaitNode();
                else if (!queued)
                    queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                         q.next = waiters, q);
                else if (timed) {
                    // 有超時的掛起
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        removeWaiter(q);
                        return state;
                    }
                    LockSupport.parkNanos(this, nanos);
                }
                else
                    // 掛起
                    LockSupport.park(this);
            }
        }
    

    run 方法執行完畢後會進行釋放

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