今天遇到一个很诡异的问题,在多款Android 4.2.2版本的手机上发现处理后台任务的服务对前台请求毫无响应。这里的后台服务是一个RemoteService,目的为了处理更新&上传等任务。本来以为是跨进程组件间通讯出现兼容性问题,后来根据分析发现问题没有那么简单,这里记录下问题原因。
第一个错误 在后台服务处理任务请求时,使用了自定义线程池处理异步任务,而Java 线程池java.util.concurrent.ThreadPoolExecutor会Catch住所有异常,即便是你运行例如下面的代码也不会抛出异常。
1 2 3 4 5 6 7 8 9 10 Runnable 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 16 public 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 13 int 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 23 private 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 27 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, // new ArrayBlockingQueue<Runnable>(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); }
参考文档
捕获Java线程池执行任务抛出的异常