事故案例分享:线程池引发的app crush

问题描述

为安卓应用提供了一个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>
事故案例分享:线程池引发的app crush

问题分析

抛出异常的思路是没有问题的,因为sdk没有合适的方式处理这个异常,需要通知上层此时的SDK是不能工作的;

主要问题是这个异常抛出去了,但并没有抛给方法的调用者,而是抛出到另一个线程。

所以应该解决的问题是将异常抛给初始化方法的调用者;


事故案例分享:线程池引发的app crush


解决方案

方案一:

将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时效果最好;


事故案例分享:线程池引发的app crush


分享到:


相關文章: