1. 核心概念简述

同步(Synchronous)与异步(Asynchronous)的核心区别在于调用者是否需要等待结果返回才能继续执行后续操作。

  • 同步:调用发出后,调用者必须挂起或阻塞,直到被调用方法执行完毕并返回结果,才能继续往下执行。就像打电话,对方不接听你就一直拿着电话等。
  • 异步:调用发出后,调用者立刻返回并继续执行后续操作,不需要等待被调用方法结束。被调用方法通常会在后台线程执行,完成后通过回调(Callback)、通知或 Future 告知调用者。就像发短信,发完你就可以去做别的事,对方回信了你再处理。

2. 原理与底层实现

线程模型视角

  • 同步交互:通常涉及一个线程贯穿始终。A 方法调用 B 方法,A 的线程执行 B 的代码,执行完再回到 A。
  • 异步交互:通常涉及多线程消息队列。A 线程发起请求后继续工作,B 任务由 C 线程(或线程池)执行。

Java 中的体现

  • 同步:普通的方法调用,synchronized 锁等待。
  • 异步
    • new Thread(() -> { ... }).start():最原始的异步。
    • Future:JDK 5 引入,允许先拿到一个“凭证”,稍后阻塞获取结果。
    • CompletableFuture:JDK 8 引入,真正的异步编排,支持回调(thenApply, whenComplete),实现了非阻塞的链式调用。

3. 性能与架构考量

  1. 吞吐量与资源利用率
    • 同步:简单直观,但如果涉及 I/O 操作(如数据库、网络),线程会被长时间阻塞,导致 CPU 资源浪费。在高并发场景下,需要大量线程才能支撑,容易耗尽资源。
    • 异步:能显著提高系统的吞吐量响应能力。线程不阻塞在 I/O 上,可以处理更多请求(如 Netty 的 Reactor 模型)。
  2. 复杂度与调试
    • 同步:代码逻辑线性,易于阅读、调试和异常捕获。
    • 异步:代码跳跃,可能出现“回调地狱”(Callback Hell),异常处理复杂,调试时栈信息可能不连续。

4. 总结与示例

回答总结: “同步与异步的关键在于调用者是否阻塞等待。同步是调用后必须等结果,逻辑简单但阻塞资源;异步是调用后立刻返回,结果通过回调或 Future 获取,能提升吞吐量但增加了编码复杂度。在高性能 Web 服务(如 Netty、Node.js)和微服务调用中,异步非阻塞是主流模式。”

代码示例:

// 1. 同步调用
public String syncCall() {
    // 必须等待 doSomething 耗时操作完成
    String result = doSomething(); 
    return result;
}

// 2. 异步调用 (使用 CompletableFuture)
public void asyncCall() {
    CompletableFuture.supplyAsync(() -> {
        // 在 ForkJoinPool 线程中执行耗时操作
        return doSomething();
    }).thenAccept(result -> {
        // 结果计算完成后,自动回调此方法
        System.out.println("异步结果: " + result);
    });
    
    // 主线程不等待,继续执行
    System.out.println("主线程继续...");
}