一, 前言
在 Java 中多线程之间是通过共享内存进行通信的, 在 go 中多线程之间通信是基于消息的, go 中的通道是 go 中多线程通信的基石.
在 java 中创建的线程是与 OS 线程一一对应的, 而在 go 中多个协程 (goroutine) 对应一个逻辑处理器, 每个逻辑处理器与 OS 线程一一对应.
每个线程要运行必须要在就绪状态情况下获取 CPU, 而操作系统是基于时间片轮转算法来调度线程占用 CPU 来执行任务的, 每个 OS 线程被分配一个时间片来占用 CPU 进行任务的执行.
在 java 中由于创建的线程与 os 线程一一对应, 所以 java 中的每个线程占用一个时间片来运行. 而 go 中多个协程对应一个 os 线程, 也就是多个协程对应了一个时间片, go 则使用自己的调度策略 (非 os 的调度策略) 来让多个协程使用一个时间片来并发的运行. 也就是 go 中存在两级策略, 一个是 go 语言层面的调度多个协程公用一个时间片, 一个是 os 层面的调度多个逻辑处理器轮询占用不同的时间片.
二, 创建协程
在 java 中创建一个线程:
- public static void main(String[] args) {
- Thread thread = new Thread(() -> {
- //run method dosomthing
- });
- thread.start();
- }
注: 如上代码创建了一线程并启动, 在 java 中存在用户线程与 deamon 线程之分, 当不存在用户线程时候, jvm 进程就退出了(而不管 main 函数所在线程是否已经结束).
在 go 中创建一个协程:
- func main() {
- // 开启一个协程, 执行匿名函数里面的内容
- go func(){
- //do somthing
- fmt.Println("im a go 协程")
- }()
- // 休眠 10s
- time.Sleep(10 * time.Second)
- }
注意: 如上通过 go 关键字开启一个协程, 执行匿名函数里面的内容, 这里需要注意 main 函数所在线程需要休眠以下, 以便等开启的协程执行, 这是因为 go 中只要 main 函数线程退出则进程就退出.
三, 同步
在 java 中我们可以使用 Semaphore,CountDownLatch,CyclicBarrier 等进行多线程之间同步, 比如下面例子:
- public final static Semaphore SEMAPHORE = new Semaphore(0);
- public static void main(String[] args) throws InterruptedException {
- Thread thread1 = new Thread(() -> {
- try {
- //run method dosomthing
- System.out.println(Thread.currentThread().getName() + "done");
- }finally {
- SEMAPHORE.release();
- }
- },"thread-1");
- thread1.start();
- Thread thread2 = new Thread(() -> {
- try {
- //run method dosomthing
- System.out.println(Thread.currentThread().getName() + "done");
- }finally {
- SEMAPHORE.release();
- }
- },"thread-2");
- thread2.start();
- System.out.println("wait all sub thread end");
- SEMAPHORE.acquire(2);
- System.out.println("all sub thread end");
- }
在 go 中也有类似的工具类:
- // 创建同步器
- var wg sync.WaitGroup
- func main() {
- // 两个信号
- wg.Add(2)
- // 开启一个协程, 执行匿名函数里面的内容
- go func() {
- // 信号量减去 1
- defer wg.Done()
- //do somthing
- fmt.Println("im A go 协程")
- }()
- // 开启一个协程, 执行匿名函数里面的内容
- go func() {
- // 信号量减去 1
- defer wg.Done()
- //do somthing
- fmt.Println("im B go 协程")
- }()
- fmt.Println("wait all sub thread end")
- wg.Wait()
- fmt.Println("all sub thread end")
- }
四, 通道
go 中通道分为有缓冲和无缓冲的, 本节我们看如何使用有缓冲通道实现生产消费模型
- var wg sync.WaitGroup
- func printer(ch chan int) {
- for i := range ch {
- fmt.Println(i)
- }
- wg.Done()
- }
- func main() {
- //1 为携程创建等待
- wg.Add(1)
- //2 创建缓冲通道
- ch := make(chan int ,10)
- //3 开启 go 协程
- go printer(ch)
- //4 写入到通道
- for i := 1; i < 100; i++ {
- ch <- i;
- }
- //5 关闭协程
- close(ch)
- fmt.Println("wait sub thread over")
- //6 等待携程结束
- wg.Wait()
- fmt.Println("main thread over")
- }
如上代码 2 创建了一个可以缓冲 10 个 int 元素的通道, 代码 3 开启一个线程用来从通道里面读取数据, 代码 4 在主线程里面写入数据到通道, 代码 5 关闭通道(关闭后不能再向通道写入数据, 但是可以从中读取).
上面例子如果用 Java 来写, 首先需要创建一个并发安全的队列, 然后开启一个生产线程写入数据到队列, 开启一个线程从队列读取元素.
五, 总结
本文我们简单的对比了 Java 和 go 中如何处理并发问题的, 后面我们在逐个详细的探讨.
来源: https://yq.aliyun.com/articles/689785