1.ThreadPoolExcuter原理說明
首先我們要知道為什麼要使用ThreadPoolExcuter,具體可以看看文件中的說明:
執行緒池 ThreadPoolExcuter 可以解決兩個不同問題:由於減少了每個任務的呼叫開銷,在執行大量的非同步任務時,它通常能夠提供更好的效能,並且還可以提供繫結和管理資源(包括執行集合任務時使用的執行緒)的方法。每個 ThreadPoolExecutor還維護著一些基本的統計資料,如完成的任務數。
執行緒池 ThreadPoolExcuter 做的其實可以看得很簡單,其實就是把你提交的任務(task)進行排程管理執行,但這個排程的過程以及其中的狀態控制是比較複雜的。
2.初始化引數介紹
可以直接看最完整的ThreadPoolExcuter的初始化函式:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... }
逐個介紹如下:
corePoolSize:核心執行緒數,在ThreadPoolExcutor中有一個與它相關的配置:allowCoreThreadTimeOut(預設為false),當allowCoreThreadTimeOut為false時,核心執行緒會一直存活,哪怕是一直空閒著。而當allowCoreThreadTimeOut為true時核心執行緒空閒時間超過keepAliveTime時會被回收。
maximumPoolSize:最大執行緒數,執行緒池能容納的最大執行緒數,當執行緒池中的執行緒達到最大時,此時新增任務將會採用拒絕策略,預設的拒絕策略是丟擲一個執行時錯誤(RejectedExecutionException)。值得一提的是,當初始化時用的工作佇列為LinkedBlockingDeque時,這個值將無效。
keepAliveTime:存活時間,當非核心空閒超過這個時間將被回收,同時空閒核心執行緒是否回收受allowCoreThreadTimeOut影響。
unit:keepAliveTime的單位。
workQueue:任務佇列,常用有三種佇列,即SynchronousQueue,LinkedBlockingDeque(無界佇列),ArrayBlockingQueue(有界佇列)。
threadFactory:執行緒工廠,ThreadFactory是一個介面,用來建立worker。通過執行緒工廠可以對執行緒的一些屬性進行定製。預設直接新建執行緒。
RejectedExecutionHandler:也是一個介面,只有一個方法,當執行緒池中的資源已經全部使用,新增新執行緒被拒絕時,會呼叫RejectedExecutionHandler的rejectedExecution法。
預設是丟擲一個執行時異常。
這麼多引數看起來好像很複雜,所以Java貼心得為我們準備了便捷的API,即可以直接用Executors建立各種執行緒池。分別是:
//建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
//通過設定corePoolSize為0,而maximumPoolSize為Integer.Max_VALUE(Int型資料最大值)實現。
ExecutorService cache = Executors.newCachedThreadPool();
//建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
//通過將corePoolSize和maximumPoolSize的值設定為一樣的值來實現。 ExecutorService fixed = Executors.newFixedThreadPool(num);
//建立一個定長執行緒池,支援定時及週期性任務執行。
//通過將佇列引數workQueue設定為DelayWorkQueue來實現。 ExecutorService schedule = Executors.newScheduledThreadPool(5);
//建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
//通過將corePoolSize和maximumPoolSize都設定為1來實現。 ExecutorService single = Executors.newSingleThreadExecutor();
這幾個API會根據具體的情況而使用預設定好預設的初始化引數去建立一個ThreadPoolExecutor。
這裡需要做一個額外說明,在ThreadPoolExcuter中,worker和task是有區別的,task是使用者提交的任務,而worker則是用來執行task的執行緒。在初始化引數中,corePoolSize和maximumPoolSize都是針對worker的,而workQueue是用來存放task的。
3.worker介紹
前面有介紹了一下worker和task的區別,其中task是使用者提交的執行緒任務,而worker則是ThreadPoolExecutor自己內部實現的一個類了。
具體原始碼如下:
/** * Woker主要維護著執行task的worker的中斷控制資訊,以及其他小記錄。這個類擴充AbstractQueuedSynchronizer * 而來簡化獲取和釋放每一個任務執行中的鎖。這可以防止中斷那些打算喚醒正在等待其他執行緒任務的任務,而不是 * 中斷正在執行的任務。我們實現一個簡單的不可重入鎖而不是ReentrantLo,因為我們不想當其呼叫setCorePoolSize * 這樣的方法的時候能獲得鎖。 */ //worker主要是對進行中的任務進行中斷控制,順帶著對其他行為進行記錄 private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ //正在跑的執行緒,如果是null標識factory失敗 final Thread thread; /** Initial task to run. Possibly null. */ //初始化一個任務以執行 Runnable firstTask; /** Per-thread task counter */ //每個執行緒計數 volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) * 用給定的first task和從threadFactory建立 */ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ //主要呼叫了runWorker public void run() { runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. //鎖方法 // protected boolean isHeldExclusively() { return getState() != 0; } //嘗試獲取鎖 protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } //嘗試釋放鎖 protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } }
ThreadPoolExcuter 中的Worker其實可以看作高階一點的執行緒。其中繼承AbstractQueuedSynchronizer主要是為了實現鎖控制。ThreadPoolExecutor會持有並管理Worker,在Worker中firstTask其實就是存放task的,而thread則是存放當前Worker本身的執行緒。
其中比較重要的就是run方法了,但這個方法其實又是去呼叫ThreadPoolExecutor裡面的runWorker()方法,具體可以看下一節的介紹。
4.ctl介紹以及執行狀態說明
首先需要介紹執行緒池有五種執行狀態:
RUNNING(狀態值-1): 接收新任務並處理佇列中的任務
SHUTDOWN(狀態值0): 不接收新任務但會處理佇列中的任務。
STOP(狀態值1): 不接收新任務,不處理佇列中的任務,並中斷正在處理的任務
TIDYING(狀態值2): 所有任務已終止,workerCount為0,處於TIDYING狀態的執行緒將呼叫鉤子方法terminated()。
TERMINATED(狀態值3): terminated()方法完成。
然後我們可以看看ThreadPoolExcuter中的ctl這個變數。
ctl是ThreadPoolExcuter中比較有意思的一個實現,它是一個AtomicInteger,這裡不對AtomicInteger多做討論,只要知道可以把它看成有原子性的Integer就夠了,其實它具有原子性的原理是使用了CAS的技術,這是一種樂觀鎖的具體實現。
ThreadPoolExcuter是將兩個內部值打包成一個值,即將workerCount和runState(執行狀態)這兩個值打包在一個ctl中,因為runState有5個值,需要3位,所以有3位表示
runState,而其他29位表示為workerCount。
而執行時要獲取其他資料時,只需要對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; // runState is stored in the high-order bits 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; private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl
//拆包ctl,分別獲取runState和WorkerCount 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; }
5.拒絕策略
當執行器(Executor)處於終止狀態,或者執行器在max threads和工作佇列都是有界並且處於飽和的時候,新提交的任務會被拒絕。在任一情況下,執行的任務將呼叫RejectedExecutionHandler的方法rejectedExecution(Runnable, ThreadPoolExecutor)。有以下四種拒絕策略:
1.預設的是ThreadPoolExecutor.AbortPolicy,在這種策略下,處理器會在拒絕後丟擲一個執行異常RejectedExecutionException。
2.在ThreadPoolExecutor.CallerRunsPolicy的策略下,執行緒會呼叫它直接的execute來執行這個任務。這種方式提供簡單的反饋控制機制來減緩新任務提交的速度。
3.在ThreadPoolExecutor.DiscardPolicy策略下,無法執行的任務將被簡單得刪除掉。
4.在ThreadPoolExecutor.DiscardOldestPolicy策略下,如果executor沒有處於終止狀態,在工作佇列頭的任務將被刪除,然後會重新執行(可能會再次失敗,這會導致重複這個過程)。
總結:本篇初步介紹了ThreadPoolExcuter的基本原理,解決了什麼問題。而後說明了ThreadPoolExcuter中的初始化引數,對其中的各個引數做初步介紹。再之後介紹ctl變數的作用,並初步介紹了任務提交失敗後的拒絕策略。