Go 并发基础:goroutine 与 channel(面向 Java 开发者)
如果你是 Java 后端,第一次接触 Go 并发,大概率会有一个感觉:
“怎么这么简单?是不是哪里藏了坑?”
这篇文章我们就用 Java → Go 的对照视角,把 Go 并发里最核心的两个概念讲清楚:
一、先抛结论:Go 并发和 Java 并发“思路完全不同”Java 并发的核心是:
一句话总结:
Java:多个线程抢同一块内存,用锁保证安全
Go 并发的核心是:
一句话总结:
Go:不共享内存,通过通信来传递数据
这不是语法差异,而是并发哲学的差异。
二、goroutine:比 Java Thread 轻太多了1️⃣ Java 创建一个线程
new Thread(() -> {
System.out.println("hello");
}).start();
问题你一定很熟:
2️⃣ Go 创建一个 goroutine
go func() {
fmt.Println("hello")
}()
只有一个 go 关键字。
3️⃣ goroutine 到底轻在哪里?
所以在 Go 里:
for i := 0; i < 100000; i++ {
go work(i)
}
这是完全正常的写法。
三、channel:Go 并发的灵魂
如果说 goroutine 是“人”,
那 channel 就是 goroutine 之间说话的方式。
1️⃣ 一个最简单的 channel 示例
ch := make(chan int)
go func() {
ch <- 10 // 发送
}()
v := <-ch // 接收
fmt.Println(v)
你可以把 channel 理解成:
一个类型安全、可阻塞的队列
2️⃣ channel = 数据通道 + 同步工具
这一点对 Java 程序员非常重要:
ch <- data
这行代码可能会阻塞,直到:
️ Go 把“同步”内置进了通信模型
3️⃣ Java 视角下的类比
但要注意:
channel 不只是队列,更是并发设计工具
四、无缓冲 vs 有缓冲 channel1️⃣ 无缓冲 channel(默认)
ch := make(chan int)
特点:
done := make(chan struct{})
go func() {
doWork()
done <- struct{}{}
}()
<-done // 等待完成
2️⃣ 有缓冲 channel
ch := make(chan int, 3)
特点:
五、一个典型并发模式:fan-out / fan-in多 goroutine 并发处理任务
jobs := make(chan int)
results := make(chan int)
for i := 0; i < 5; i++ {
go worker(jobs, results)
}
for j := 0; j < 10; j++ {
jobs <- j
}
close(jobs)
for i := 0; i < 10; i++ {
<-results
}
你会发现:
但并发逻辑非常清晰
六、那 Go 还要不要锁?
要,但不是第一选择。
Go 的推荐顺序是:
优先用 channel实在不合适,再用 sync.Mutex不要一上来就锁
这也是 Go 官方名言:
Don’t communicate by sharing memory; share memory by communicating.
七、给 Java 程序员的几个“避坑提醒”️ 1. main goroutine 结束,程序直接退出
go doWork()
如果 main 函数结束了,goroutine 不会等你。
️ 用 WaitGroup 或 channel 控制生命周期。
️ 2. 不要随意 close channel️ 3. 并发不是越多越好
goroutine 很轻,但:
依然是瓶颈。
八、总结一句话
Go 用 goroutine 降低并发成本,用 channel 降低并发复杂度。
如果你是 Java 开发者,转 Go 最大的转变不是语法,而是:
从“锁思维”切换到“通信思维”
