開發第三方庫的時候往往不會用到rxjava,會使用Thread來處理線程。那麼就直接開門見山,讓我們一步一步解讀ThreadUtils源碼。作者閱讀源碼習慣是先從public方法一個一個硬讀下去,再配合使用方法來使用。
public static boolean isMainThread()
這個方法很簡單,調用Android原生代碼Looper來判斷是否是主線程,關於Android代碼Looper在第四章上會有詳解public static Handler getMainHandler()
代碼是直接返回HANDLER,而這個HANDLER是一個全局靜態主線程的變量 new Handler(Looper.getMainLooper());public static void runOnUiThread(final Runnable runnable)
在ui主線程上執行事件,會先通過Looper.myLooper()判斷當前線程是否是ui主線程,如果是直接運行事件,如果不是,則通過上面的HANDLER來運行事件public static ExecutorService getFixedPool(@IntRange(from = 1) final int size)
該方法返回一個線程池,size參數則是線程池中線程數量,從註釋和方法名中可以知道該方法FixedPool是個對線程數做限制的線程池,用於併發壓力的場景下,那麼我們沿着這個方法看看怎麼實現創建線程池的private static ExecutorService getPoolByTypeAndPriority(final int type)
getFixedPool
方法調用了該方法,該方法運行代碼getPoolByTypeAndPriority(type, Thread.NORM_PRIORITY);Thread.NORM_PRIORITY是個優先級,優先級是5,而線程中是1-10,優先級數字越大則是更優先,爲什麼getFixedPool
方法的size參數到這個方法變成了type參數了呢?因爲其他type參數都是負數的,而這個傳遞必須是1以上,所以這個設置xx數量的線程方法,已經自動歸納爲另一個類型了。private static ExecutorService getPoolByTypeAndPriority(final int type, final int priority)
該方法用到了全局靜態Map<Integer, Map<Integer, ExecutorService>> TYPE_PRIORITY_POOLS,是個通過類型存儲線程池的map,完整代碼註釋如下
/**
* 根據類型獲取線程池
* @param type 類型
* @param priority 優先級
* @return 線程池
*/
private static ExecutorService getPoolByTypeAndPriority(final int type, final int priority) {
// 同步map安全,防止線程不安全創建多個 Map線程池
synchronized (TYPE_PRIORITY_POOLS) {
ExecutorService pool;
// 通過類型獲取 Map線程池
Map<Integer, ExecutorService> priorityPools = TYPE_PRIORITY_POOLS.get(type);
if (priorityPools == null) {
// 如果沒有 Map線程池 則新建一個
priorityPools = new ConcurrentHashMap<>();
pool = ThreadPoolExecutor4Util.createPool(type, priority);
// 加入線程池
priorityPools.put(priority, pool);
// 新建後加入 Map線程池
TYPE_PRIORITY_POOLS.put(type, priorityPools);
} else {
// 根據線程池優先級獲取線程池
pool = priorityPools.get(priority);
// 如果沒有該線程池,則創建新的線程池
if (pool == null) {
pool = ThreadPoolExecutor4Util.createPool(type, priority);
priorityPools.put(priority, pool);
}
}
return pool;
}
}
從上面代碼可知Map TYPE_PRIORITY_POOLS裏面包含了Map ExecutorService,
也就是說線程池包含進了兩層map,最外的第一層是key爲線程池類型的,裏面的第二層是key爲優先級的。
ThreadPoolExecutor4Util.createPool
在上面代碼註釋中可知這句核心關鍵代碼,創建線程池,下面詳細階段這段代碼static final class ThreadPoolExecutor4Util extends ThreadPoolExecutor
該類繼承了ThreadPoolExecutor來創建線程池,分別創建了以下幾種線程,跟rxjava是不是有點像呢
/**
* 創建線程池
* @param type 類型
* @param priority 優先級
* @return 線程池
*/
private static ExecutorService createPool(final int type, final int priority) {
switch (type) {
case TYPE_SINGLE:
// 創建 核心線程數爲1,線程池最大線程數量爲1,非核心線程空閒存活時長爲0
// 只創建一個線程確保 順序執行的場景,並且只有一個線程在執行
return new ThreadPoolExecutor4Util(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue4Util(),
new UtilsThreadFactory("single", priority)
);
case TYPE_CACHED:
// 創建 核心線程數爲0,線程池最大線程數量爲128,非核心線程空閒存活時長爲60秒
// 線程數爲128個一般用於處理執行時間比較短的任務
return new ThreadPoolExecutor4Util(0, 128,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue4Util(true),
new UtilsThreadFactory("cached", priority)
);
case TYPE_IO:
// 創建 核心線程數爲可計算資源*2+1,線程池最大線程數量爲可計算資源*2+1,非核心線程空閒存活時長爲30秒
return new ThreadPoolExecutor4Util(2 * CPU_COUNT + 1, 2 * CPU_COUNT + 1,
30, TimeUnit.SECONDS,
new LinkedBlockingQueue4Util(),
new UtilsThreadFactory("io", priority)
);
case TYPE_CPU:
// 創建 核心線程數爲可計算資源+1,線程池最大線程數量爲可計算資源*2+1,非核心線程空閒存活時長爲30秒
return new ThreadPoolExecutor4Util(CPU_COUNT + 1, 2 * CPU_COUNT + 1,
30, TimeUnit.SECONDS,
new LinkedBlockingQueue4Util(true),
new UtilsThreadFactory("cpu", priority)
);
default:
// 創建 核心線程數、線程池最大數量爲自定義的,空閒存活時長爲0
return new ThreadPoolExecutor4Util(type, type,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue4Util(),
new UtilsThreadFactory("fixed(" + type + ")", priority)
);
}
}
除了創建,還重寫了afterExecute方法和execute方法,不過這兩個方法暫時沒什麼意義所以先不管。
在線程系列第二節講到了創建線程池需要的ThreadFactory threadFactory(線程工廠)、BlockingQueue workQueue(任務隊列),該ThreadUtils類自定義了線程工廠和任務隊列,所以我們先了解該兩類
-
private static final class LinkedBlockingQueue4Util extends LinkedBlockingQueue<Runnable>
我們先了解LinkedBlockingQueue類,LinkedBlockingQueue這個隊列接收到任務的時候,如果當前線程數小於核心線程數,則新建線程(核心線程)處理任務;如果當前線程數等於核心線程數,則進入隊列等待。由於這個隊列沒有最大值限制,即所有超過核心線程數的任務都將被添加到隊列中,這也就導致了 maximumPoolSize 的設定失效,因爲總線程數永遠不會超過 核心線程數。
該類重寫了offer方法,如果看過線程池的源碼的同學,會發現如下圖:
是的,執行pool.execute後會執行隊列的offer方法,那爲什麼重寫offer方法呢,通過上面的解釋可以知道,LinkedBlockingQueue的總線程數永遠不會超過核心線程數,通過重寫它,可以自定義TYPE_CACHED類型的線程池的核心線程爲0,總線程數128。 static final class UtilsThreadFactory extends AtomicLong implements ThreadFactory
線程工廠類,該類重寫只是簡單的創建了一個Thread返回
那麼在這裏我們基本知道了創建線程池的流程了,接下來,是我們調用該工具類時調用線程的整個流程是怎樣的,通過斷點調試,我們是最容易瞭解這個過程的,如圖:
-
onClick
和testSingle
都是app上業務使用的方法 -
public static <T> void executeByCached(final BaseTask<T> baseTask)
關鍵代碼是直接創建了一個任務BaseTask傳遞到線程池使用,那麼我們看看BaseTask方法,已經針對run方面的代碼進行了全部註釋,更多的註釋可以下載源碼觀看
@Override
public void run() {
// 判斷是否循環計劃內的
if (isSchedule) {
// 因爲如果是在循環內的,那麼runner還是之前的
if (runner == null) {
// 判斷當前狀態如果是New,便賦值state=RUNNING,如果不是New,便返回
if (!state.compareAndSet(NEW, RUNNING)) {
return;
}
// 獲取當前線程
runner = Thread.currentThread();
if (mTimeoutListener != null) {
Log.w("ThreadUtils", "Scheduled task doesn't support timeout.");
}
} else {
// 如果不是RUNNING便直接返回
if (state.get() != RUNNING) {
return;
}
}
} else {
// 判斷當前狀態如果是New,便賦值state=RUNNING,如果不是New,便返回
if (!state.compareAndSet(NEW, RUNNING)) {
return;
}
// 獲取當前線程
runner = Thread.currentThread();
if (mTimeoutListener != null) {
// 實例化 循環或延遲任務的線程池
mExecutorService = new ScheduledThreadPoolExecutor(1, (ThreadFactory) Thread::new);
// 調用了延遲運行任務
mExecutorService.schedule(new TimerTask() {
@Override
public void run() {
if (!isDone() && mTimeoutListener != null) {
timeout();
mTimeoutListener.onTimeout();
}
}
}, mTimeoutMillis, TimeUnit.MILLISECONDS);
}
}
try {
// 執行doInBackground方法獲取值
final T result = doInBackground();
// 判斷是否循環計劃內的
if (isSchedule) {
// 如果不是RUNNING便直接返回
if (state.get() != RUNNING) {
return;
}
getDeliver().execute(() -> onSuccess(result));
} else {
// 判斷當前狀態如果是RUNNING,便賦值state=COMPLETING,如果不是RUNNING,便返回
if (!state.compareAndSet(RUNNING, COMPLETING)) {
return;
}
// 執行成功方法,getDeliver()已經封裝了跳轉ui線程
getDeliver().execute(() -> {
onSuccess(result);
onDone();
});
}
} catch (InterruptedException ignore) {
// 被中斷了,判斷當前狀態如果是CANCELLED,便賦值state=INTERRUPTED
state.compareAndSet(CANCELLED, INTERRUPTED);
} catch (final Throwable throwable) {
// 如果出現異常了,判斷當前狀態如果是RUNNING,便賦值EXCEPTIONAL
if (!state.compareAndSet(RUNNING, EXCEPTIONAL)) {
return;
}
// 執行成功方法,getDeliver()已經封裝了跳轉ui線程
getDeliver().execute(() -> {
onFail(throwable);
onDone();
});
}
}
-
cancel
ThreadUtils的作者沒明說Activity銷燬是否要寫cancel事件
從上面整個流程可以幾個核心方法,我概括出來:
- 擁有者跟rxjava類似的doInBackground,onSucces等等方法
- 通過源碼得知有延時、循環週期運行線程等方法
- 根據使用方式不一樣來決定io,cpu線程數量,緩存時間,是否使用隊列