ExecutorService如何非阻塞运行 解决HTTP请求阻塞问题
问题根源
你的代码出现阻塞的核心原因是异步逻辑完全写反了:
- 你虽然用了ExecutorService提交任务,但转头就通过主线程Handler把最重的Jsoup解析逻辑
processSourceCode()全部post回了Android主线程执行。 - Android主线程是单线程串行处理所有消息的,耗时的Jsoup解析占住主线程时,后续的HTTP请求发起逻辑、其他网络回调都会被堵在消息队列里,必须等前一个解析任务跑完才能执行,和你观察到的「停用响应处理就没有阻塞」的现象完全一致。
- 你配置的100线程的线程池完全没有承担任何耗时计算工作,只是做了一次无意义的线程跳转。
修复步骤
- 把耗时逻辑全部留在子线程执行,只有UI操作才切主线程。不要把整个解析任务post回主线程,Jsoup解析属于CPU密集型耗时操作,直接放在线程池的工作线程执行即可,解析完成后如果需要更新页面,再把轻量的UI操作切回主线程。
- 优化线程池配置。不要每个
MakeRequest实例都新建一个核心数100的线程池,线程是重量级系统资源,无限制创建会导致频繁的CPU调度、内存占用过高,全局共用一个适配当前设备核心数的线程池即可。 - 确认网络回调线程配置。如果你用的网络客户端默认把响应回调派发到主线程(比如OkHttp默认Callback、HttpURLConnection的常见封装),只要主线程没有被耗时任务占住,直接在回调里发起下一个请求不会有阻塞问题。
修正后代码参考
MakeRequest.java
class MakeRequest{ // 全局共用线程池,按CPU核心数配置合理的线程数,IO+计算混合任务取核心数*2即可 public static final ExecutorService executor = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors() * 2 ); private final Handler mainHandler = new Handler(Looper.getMainLooper()); public void sentDataToJsoup(final String html, final String sentUrl) { executor.execute(new Runnable() { @Override public void run() { // 耗时的Jsoup解析直接在子线程执行,不占用主线程 MyJsoup jsoupProcessor = new MyJsoup(html, sentUrl); // processSourceCode返回解析后的结构化结果 final Object parseResult = jsoupProcessor.processSourceCode(); // 仅当需要更新UI、操作主线程组件时,才切回主线程执行轻量操作 mainHandler.post(new Runnable() { @Override public void run() { // 此处仅写UI更新逻辑,比如刷新列表、展示结果,禁止放耗时任务 } }); } }); } // 页面销毁时调用,避免线程池泄漏 public static void release(){ executor.shutdown(); } }
MainPage.java 逻辑说明
修正后sentDataToJsoup方法提交异步任务后会立刻返回,不会等待解析完成,因此processHttpResponse里的执行顺序是:
- 提交请求1的解析任务到线程池(立刻返回,不阻塞)
- 立刻执行
request2.makeSecondRequest()发起第二个请求 - 线程池中空闲的工作线程会自动处理请求1的响应解析,完全不阻塞后续请求的发送和接收
额外注意事项
- 不要在主线程执行任何超过100ms的操作,包括Jsoup解析、文件读写、数据库操作,所有耗时逻辑全部下沉到工作线程。
- 如果需要在解析完成后触发后续请求,把请求发起逻辑放在解析任务的子线程部分即可,不用切主线程。
- 如果需要处理并发请求的响应顺序问题,可以给每个请求加唯一标识,避免响应结果错位。
内容的提问来源于stack exchange,提问作者Chijioke Francis
相关产品推荐
相关产品推荐

