問題描述
為安卓應用提供了一個SDK,由於初始化操作比較耗時,因此SDK在初始化方法內部利用多線程並行進行初始化。
由於某些原因導致初始化時開啟的某個線程拋出了異常,這個異常並不能被上層捕獲導致app crush;
代碼的大體邏輯如下:
<code> ExecutorService executors = Executors.newFixedThreadPool(4); //app 本身的線程池
executors.execute(() -> {//執行初始化方法
//模擬SDK的初始化方法
ExecutorService internalExecutors = Executors.newFixedThreadPool(4);
internalExecutors.execute(() -> {
throw new RuntimeException("拋個異常。");
});
internalExecutors.shutdown();
while (!internalExecutors.isTerminated()) {
}
});/<code>
問題分析
拋出異常的思路是沒有問題的,因為sdk沒有合適的方式處理這個異常,需要通知上層此時的SDK是不能工作的;
主要問題是這個異常拋出去了,但並沒有拋給方法的調用者,而是拋出到另一個線程。
所以應該解決的問題是將異常拋給初始化方法的調用者;
解決方案
方案一:
將SDK內部的多線程改為單線程,這樣可以很自然的將異常拋給“初始化方法”的調用者;但會增加一定的耗時;
方案二:
仍然利用線程池並行處理,但是確保線程池使用excute執行的任務不會拋出異常;
前面也說了,拋出異常的思路是沒有問題的,那麼在發生異常時就需要處理並保存異常,這個異常還應該可以被初始化方法的主線程獲取到,然後拋給調用者;
如果讓每個task來處理自己的異常,顯然會造成代碼的冗餘,而且一旦異常處理邏輯需要修改,則可能會涉及到多出修改,所以最好有一種類似“切面”的東西來處理異常;
具體方案如下:
<code> /**
* 自定義線程工廠,記錄異常(根據我們的實際場景,只記錄一個即可)
*/
static class ExceptionRecordThreadFactory implements ThreadFactory {
private volatile Throwable exception = null;
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setUncaughtExceptionHandler((t, e) -> {
if (exception == null) {
synchronized (ExceptionRecordThreadFactory.class) {
if (exception == null) {
exception = e;
}
}
}
System.out.println(t.getName() + "\\t" + e.getMessage());
});
return th;
}
public Throwable getException() {
return exception;
}
}/<code>
創建線程池時,使用這個線程池所需要執行的線程都通過線程工廠來創建;
<code> ExecutorService executors = Executors.newFixedThreadPool(4);//app 本身的線程池
executors.execute(() -> {//執行初始化方法
//模擬SDK的初始化方法
ExceptionRecordThreadFactory threadFactory = new ExceptionRecordThreadFactory();
ExecutorService internalExecutors = Executors.newFixedThreadPool(4, threadFactory);
internalExecutors.execute(() -> {
throw new RuntimeException("拋個異常。");
});
internalExecutors.shutdown();
while (!internalExecutors.isTerminated()) {
}
System.out.println("exception info:" + (threadFactory.getException() == null ? "未發現異常" : threadFactory.getException().getMessage()));
});/<code>
其他方面分析
由於我們的目的很簡單,就是需要7、8個任務併發執行,縮短初始化時間;
因此直接使用JDK提供的Executors創建固定線程池,但一般情況下不推薦這樣創建線程池,具體理由在後面的文章中分析;
另外線程池是創建在方法內部的,方法執行完之後就會GC,但一般來講都是全局設置一個線程池;從這個維度來講,也並不是很合理,這是由於整體架構導致的,負責SDK的並沒有全局的上下文;
為什麼使用線程池,而不是單獨啟動7、8個線程,因為測試中發現並不是線程數越多越好,一般設置為CPU核數的1/2時效果最好;
閱讀更多 IT技術百貨 的文章