Go | Java 线程与 go 协程的对比
〰️

Go | Java 线程与 go 协程的对比

AI custom autofill
本博客比较了 Java 线程和 Go 协程的异同。
Tags
Java
Go
Published
July 23, 2025

一 背景

最近在做一个项目设计时遇到一个下面的问题。
如果在一个项目中,处理每个任务的时间都较长,如需要 15s 才能处理完一个任务。那么一个项目处理任务的 QPS 则是一个需要考虑的性能点。
开始我简单的以为只是需要开启更多的线程来进行多线程处理。因为 15s 这个时间如果不能压缩的话,提高 QPS 就只能扩大线程池的容量,但是后来发现并非如此。
这里涉及到 java 和 go 中线程和协程的原理。
 

二 总览对比

特性
Java 线程 (Thread)
Go 协程 (Goroutine)
创建成本
高(每个线程大约消耗 1MB 栈内存)
低(初始栈仅几 KB,可动态增长)
调度方式
OS 线程,由操作系统调度(抢占式)
用户态调度,由 Go runtime 管理
数量支持
数千个线程易于耗尽资源
可以轻松支持成千上万个 goroutine
阻塞行为
阻塞会挂起整个 OS 线程
使用 channel 和非阻塞 IO,调度器会自动切换
创建方式
new Thread().start() 或线程池
go func()
通信方式
多使用共享内存 + 锁
倾向于使用 channel,CSP 模型

三 底层原理

3.1 Java 线程底层原理

  1. 线程模型
      • 每个 Thread 对应一个 内核线程(Kernel Thread),由操作系统(Windows/Linux)调度。
      • 多线程切换时需要 内核态/用户态切换(上下文切换),成本高。
  1. 内存结构
      • 每个线程分配独立的栈空间(默认 1MB)。
      • 多线程间共享堆内存,因此要注意并发同步(synchronized, Lock, volatile 等)。
  1. 调度机制
      • 操作系统级的线程调度器负责分配 CPU。
      • Java 层可使用线程池 (ExecutorService) 控制线程复用
 

3.2 Go 协程底层原理(以 Go 1.21 为例)

  1. 线程模型:Go 使用 M:N 调度模型:
    1. Go 采用 M:N 调度器,这是一个由 Go 运行时本身而非操作系统管理的高度复杂的用户空间调度器 。在此模型中,M 个协程被多路复用到 N 个操作系统线程上,其中 M 可以远大于 N。
      G-P-M 模型由三个核心组件组成:
      • 协程(G): 这些是使用 go 关键字启动的实际任务或可执行代码单元 。协程退出后,其
        • g 对象会返回到空闲池中以供重用。
      • 处理器(P): 这些是逻辑处理器,通常每个 CPU 核心创建一个(可通过 GOMAXPROCS 配置) 。每个 P 都充当协调器,维护一个可运行协程的本地运行队列,并为 OS 线程(M)执行协程(G)提供必要的“权限和资源” 。
      • 机器(M): 这些是执行 Go 用户代码、运行时代码或系统调用的原生操作系统线程 。M 是实际运行协程的“工作者”。
  1. 栈空间管理
      • 初始每个 goroutine 只有 ~2KB 栈空间。
      • 栈可以 动态增长和缩小(不是固定分配),这是协程轻量的关键。
  1. 调度器机制(Go runtime):
      • 非抢占式调度为主,定期或遇阻塞点(如 syscall、channel 操作)触发调度。
      • 从 Go 1.14 起,支持 抢占式调度,防止长时间占用 CPU。
      • 使用 Work Stealing 算法 实现高并发调度效率。
  1. 通信机制
      • 倾向于使用 channel 传递消息,避免共享内存。
      • 遵循 “Do not communicate by sharing memory; share memory by communicating.”
 

3.3 本质区别

Java Thread
Go Goroutine
属于
OS 层线程
用户层轻量线程
创建成本
极低
切换代价
高(内核切换)
低(用户态调度)
并发模型
基于线程 + 锁
基于协程 + channel
调度控制
操作系统
Go runtime 调度器
最大数量
数千个
数百万个

3.4 Demo 说明

java
public class Demo { public static void main(String[] args) { new Thread(() -> { System.out.println("Hello from Java thread"); }).start(); } }
go
package main import "fmt" func main() { go func() { fmt.Println("Hello from Go goroutine") }() }

四 总结

  • 高并发任务:选择 Go 更优,因为其 goroutine 能轻松承载百万级并发,不会受到线程创建开销和上下文切换限制。
  • 底层可控性和成熟生态:Java 在企业级应用中生态成熟,配套工具完备(如线程池、锁机制、监控工具)。
  • 阻塞处理:Java 的阻塞可能导致线程浪费;Go 的协程遇阻塞点能立即切换,不会影响整体调度。