一、前言
今天遇到一个很诡异的问题,在多款Android 4.2.2版本的手机上发现处理后台任务的服务对前台请求毫无响应。这里的后台服务是一个RemoteService,目的为了处理更新&上传等任务。本来以为是跨进程组件间通讯出现兼容性问题,后来根据分析发现问题没有那么简单,这里记录下问题原因。
二、第一个错误
在后台服务处理任务请求时,使用了自定义线程池处理异步任务,而Java 线程池java.util.concurrent.ThreadPoolExecutor会Catch住所有异常,即便是你运行例如下面的代码也不会抛出异常。
1
2
3
4
5
6
7
8
9Runnable runnable = new Runnable() {
@Override
public void run() {
Test test = null;
System.out.println(test.toString());
}
};
mExecutor.submit(runnable);
在处理线程池异常捕获的问题时,犯下了第一个错误,使用了submit方法拿到任务执行结果会阻塞当前线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public Future> submit(Runnable task) {
Future> future = mExecutor.submit(task);
try {
future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Reset interrupted status
} catch (ExecutionException e) {
Throwable exception = e.getCause();
// Forward to exception reporter
if (exception instanceof UncatchException) {
throw (UncatchException) exception;
}
}
return future;
}
三、第二个错误
在错误一的基础上,使用了一个开源并发库android-lite-go,先看它的异步任务是如何调度的。
1
2
3
4
5
6
7
8
9
10
11
12
13int coreSize = CPU_CORE;
int queueSize = coreSize * 32;
synchronized (lock) {
if (runningList.size() < coreSize) {
runningList.add(scheduler);
threadPool.execute(scheduler);
} else if (waitingList.size() < queueSize) {
waitingList.addLast(scheduler);
} else {
//...
}
}
当正在运行的任务数小于设定的coreSize时,submit的任务会提交到线程池。
coreSize由系统核心数确定。
当正在运行的任务数小于queueSize时,submit的任务会提交到等待队列中。
当一个任务执行完后,会从等待队列中获取一个任务提交给线程池执行。
在一个异步任务中使用阻塞当前线程的方法,把另一个任务提交给了executor。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private void runTaskOne() {
Runnable runnable = new Runnable() {
@Override
public void run() {
// do something
runTaskTwo();
}
};
mTaskController.submit(runnable);
}
private void runTaskTwo() {
Runnable runnable = new Runnable() {
@Override
public void run() {
// do task2
// ......
}
};
mTaskController.submit(runnable);
}
三、问题原因及解决方案
当正在运行的任务使用阻塞当前进程的submit方法提交另一个异步任务时,后提交的任务被放入等待队列等待线程池执行,但在Running list中的任务又在等待后任务执行的结果,这样就造成了死锁!!这个问题跟Android系统版本并没有关系,但因为Android 4.2.2版本的手机低端较多核心数较少,所以runningList的大小就比较小,Running List很容易就被填满。
解决方案使用非阻塞的方法提交异步任务
自定义线程池,重写afterExecute()方法,在该中获取线程池运行的异步任务运行时异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, //
new ArrayBlockingQueue(10000),//
new DefaultThreadFactory()) {
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
printException(r, t);
}
};
private static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future>) {
try {
Future> future = (Future>) r;
if (future.isDone())
future.get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
log.error(t.getMessage(), t);
}
四、参考文档