Android调用Java Spring Boot REST服务:响应返回与循环阻塞问题
问题解答
问题B:为什么等待循环无法执行,还阻止REST调用完成?
这是典型的主线程死锁+线程调度冲突问题:
- 你的
while循环在主线程里执行,会一直占用主线程的CPU资源,没有任何让步操作(比如Thread.sleep())。 - 而
AsyncHttpClient默认会把onSuccess/onFailure这类回调调度到主线程执行(Android框架里网络请求回调通常绑定主线程,方便后续更新UI)。 - 主线程被死循环死死卡住,根本腾不出时间处理回调任务,所以
callResults.put()永远不会执行,循环也永远无法退出。甚至连System.out.print(".")的输出都没法及时刷新——因为主线程被占满了,连I/O操作的调度都被延迟了。 - 最终结果就是:回调完全无法执行,看起来像服务器调用被"阻止",程序陷入永久等待。
问题A:如何将Login对象返回给初始调用的函数?
既然你不能用观察者模式(LiveData、RxJava等),那我们用正确的同步阻塞方式替代不靠谱的Map+死循环方案。推荐使用Java并发包的CountDownLatch——它是专门协调异步任务与等待线程的工具,比手动循环稳定得多。
修改后的代码示例:
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; class MainController { private final AtomicLong counter; // 用线程安全容器存储每个请求的结果和等待锁 private final ConcurrentHashMap<Long, AtomicReference<CallResult>> callResults; private final ConcurrentHashMap<Long, CountDownLatch> latches; public MainController(){ counter = new AtomicLong(); callResults = new ConcurrentHashMap<>(); latches = new ConcurrentHashMap<>(); } public Login getLoginByUsername(String username) { long callID = counter.incrementAndGet(); // AtomicReference保证多线程下结果的可见性 AtomicReference<CallResult> resultRef = new AtomicReference<>(); // 初始化计数为1的Latch:回调完成后调用countDown()解锁 CountDownLatch latch = new CountDownLatch(1); callResults.put(callID, resultRef); latches.put(callID, latch); System.out.println("Start call"); ServerConnection.get("myURL", null, new JsonHttpResponseHandler(){ @Override public void onSuccess(int statusCode, Header[] headers, JSONObject response){ try { System.out.println("Call success, enter result:"); CallResult result = new CallResult(Login.parseFromJson(response)); resultRef.set(result); System.out.println(result); } catch (JSONException e) { e.printStackTrace(); } finally { // 无论成功失败都要解锁,避免永久等待 latch.countDown(); } } @Override public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) { // 必须处理失败场景!否则请求失败会导致线程永久阻塞 throwable.printStackTrace(); latch.countDown(); } }); System.out.println("start waiting"); try { // 阻塞当前线程,直到Latch计数归0(回调执行完毕) latch.await(); } catch (InterruptedException e) { // 恢复线程中断状态,避免异常丢失 Thread.currentThread().interrupt(); e.printStackTrace(); } System.out.println("waiting over, return result:"); CallResult result = resultRef.get(); // 清理资源,避免内存泄漏 callResults.remove(callID); latches.remove(callID); return result != null ? result.content : null; } }
方案说明:
- CountDownLatch的作用:
latch.await()会阻塞当前线程,直到回调里调用latch.countDown()将计数减到0,精准等待异步任务完成,不会像死循环那样空耗CPU。 - 线程安全保障:用
ConcurrentHashMap存储请求对应的结果和Latch,避免多线程并发问题;AtomicReference保证结果在回调线程与等待线程之间的可见性。 - 异常处理:在
onFailure和onSuccess的finally块中调用countDown(),确保无论请求成功或失败,等待线程都能正常解除阻塞。 - 额外提醒:如果
getLoginByUsername在AndroidUI线程调用,latch.await()会阻塞主线程导致ANR(应用无响应)。这种情况下要确保方法在子线程执行(比如线程池),若你无法修改调用逻辑,只能尽量避免在UI线程触发该方法。
内容的提问来源于stack exchange,提问作者Benjamin Batt




