一、核心
-
同步调用:调用方必须等被调用方"完成"后才能继续执行。
-
线程阻塞调用:调用方在等待结果时,线程被挂起(不占用 CPU),直到条件满足再被唤醒。
也就是说:
同步调用关注"调用方式",线程阻塞关注"线程状态"。
二、它们不是同一个维度的概念
你可以把它们理解成:
-
同步 / 异步:描述"调用方是否需要等待结果"。
-
阻塞 / 非阻塞:描述"等待时线程是否被挂起"。
因此会出现四种组合:
- 同步阻塞(最常见)
例:Java 的 Thread.sleep() 、 Object.wait() 、 Socket.accept()
- 同步非阻塞
例:轮询一个标志位,线程不挂起但一直检查。
- 异步阻塞(很少见)
例:异步回调在另一个线程执行,但主线程调用 Future.get() 阻塞等待结果。
- 异步非阻塞(高性能网络常用)
例:Netty、NIO、JavaScript Promise、Go 的 goroutine + channel。
三、用生活例子快速理解
你去咖啡店点咖啡:
- 同步调用
你点完单后,必须等咖啡做好才能离开柜台。
- 线程阻塞调用
你点完单后,店员让你"坐着等叫号",你不需要一直站在柜台前,但你也不能去做别的事,只能等。
- 异步调用
你点完单后,店员给你一个取餐码,你可以去逛街,等咖啡好了再回来取。
- 非阻塞
你不等店员叫号,自己每隔几秒去柜台看一下"好了没"。
四、技术层面的更细解释
同步调用的特点
-
调用方和被调用方在同一个执行流程中。
-
调用方必须等待被调用方返回。
-
是否阻塞取决于实现。
例:
java
int result = add(1, 2);
System.out.println(result); // 必须等 add 返回
线程阻塞调用的特点
-
线程由操作系统挂起(进入 BLOCKED/WAITING 状态)。
-
不占用 CPU。
-
通常发生在等待 I/O、锁、条件变量、睡眠等场景。
例:
java
synchronized (lock) {
lock.wait(); // 当前线程阻塞,直到被 notify
}
五、为什么很多人会混淆?
因为大多数同步调用在底层实现上都会阻塞线程,所以大家会把"同步"和"阻塞"当成同义词。
但它们本质不同。
例如:
- 同步但不阻塞:
你循环检查一个变量,不挂起线程。
- 异步但阻塞:
你发起一个异步任务,但你立刻调用 future.get() 阻塞等待结果。