---
title: SynchronousQueue 像 channel，但 thread 和 goroutine 远不止队列差异
date: '2017-10-13 13:32:12'
draft: false
summary: 把 SynchronousQueue 类比成 channel 只抓住了表面，更关键的差异仍然在执行模型和调度方式。
slug: synchronousqueue-is-not-goroutine
syndication:
- platform: Weibo
  url: https://weibo.com/1648815335/FqaQAb6cZ
tags:
- java
- golang
- concurrency
- thread
- goroutine
topics:
- software-engineering
type: post
---

我一直觉得，把 `Java` 的 `SynchronousQueue` 和 `Go` 的 `channel` 放在一起类比，本身问题不大。

它们都可以理解成一种“同步交接”的通信原语：没有缓冲时，发送和接收必须配对，才能把数据交出去。

但很多讨论容易在这一步直接滑过去，仿佛既然 `channel ≈ SynchronousQueue`，那 `goroutine ≈ thread` 也差不多。这个结论就偏得很远了。

真正差异大的地方，不在通信原语，而在执行单元和调度模型。

如果你起 `10` 万个 `goroutine`，让它们都 `sleep 10` 秒，整体完成时间大概率还是接近 `10` 秒，因为 `goroutine` 本身非常轻，调度器会把它们复用到少量系统线程上。

但如果你在传统线程池模型里起同样数量的任务，完成时间通常就取决于线程池大小。假设线程池只有 `1000` 个工作线程，那 `10` 万个任务就会排成大约 `100` 批，一批 `10` 秒，总时间自然会被拉长到接近 `1000` 秒。

网络请求场景也类似。只要底层模型仍然是“一个阻塞任务长期占着一个线程”，那线程池大小、队列堆积、上下文切换这些问题就很难消失。

所以我会觉得，一个更准确的说法应该是：

- `channel` 和 `SynchronousQueue` 在局部语义上可以类比。
- 但 `goroutine` 和传统 `thread` 根本不是一个成本结构。
- 如果语言层和运行时层没有把轻量调度这件事做好，光在库层补几个并发原语，并不能把模型差距抹平。

这也是为什么很多年里 `Java` 世界一直在等更像 `fiber` 的方案。因为问题从来不只是“有没有一个像 channel 的队列”，而是系统是否愿意把并发从“线程资源管理”上抽象出去。

所以类比可以做，但只能做到一半。再往后走，就要回到运行时设计本身，而不是只看 API 形状像不像。
