go协程、java虚拟线程、线程池,试验对比

试验目的

本文的目的是对比Go协程、Java线程池、Java虚拟线程的切换成本,所以计算任务本身是一个空函数。

版本

$ java --version
java 22 2024-03-19

$ go version
go version go1.22.0 windows/amd64

试验代码

go代码

package main

import (
	"fmt"
	"os"
	"strconv"
	"sync"
	"time"
)

// 任务本身
func task() {
}

func main() {
	// 不需要热身
	p, _ := strconv.Atoi(os.Args[1]) //p个并发协程
	fmt.Printf("concurrency %d\n", p)
	wg := sync.WaitGroup{}
	wg.Add(p)
	begin := time.Now() //开始计时
	for i := 0; i < p; i++ {
		go func() {
			defer wg.Done()
			task() // 每个协程执行一次任务
		}()
	}
	wg.Wait()                                   //等所有任务结束
	useTime := time.Since(begin).Milliseconds() //结束计时
	fmt.Printf("use time %d ms\n", useTime)
}

// go build -o basic/VirtualThread/matmul.exe basic/VirtualThread/mat_mul.go
// ./basic/VirtualThread/matmul.exe 10000

java代码

package basic.VirtualThread;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MatMulVT{
    // 任务本身
    private static Runnable runnable = () -> {
    };

    public static void awaitTerminationAfterShutdown(ExecutorService threadPool) {
        threadPool.shutdown(); 
        try {
            if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
                threadPool.shutdownNow();
            }
        } catch (InterruptedException ex) {
            threadPool.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        // 热身,第一次不算
        int p=Integer.parseInt(args[0]);//p个并发线程
        System.out.println("concurrency "+p);
        // ExecutorService executorService = Executors.newScheduledThreadPool(10);//定长线程池
        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); //为每个任务创建一个虚拟线程
        for (int i = 0; i < 10000; i++) {  //JIT warm up 1万次 
            executorService.submit(runnable);
        }
        try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedException e){};    //等热身任务线束

        // 第二次才算。复用ExecutorService
        long startTime = System.currentTimeMillis();//开始计时
        for (int i = 0; i < p; i++) {
            executorService.submit(runnable);
        }
        awaitTerminationAfterShutdown(executorService);//等所有任务结束
        long endTime = System.currentTimeMillis();//结束计时
        System.out.println("use time "+(endTime-startTime)+" ms");
    }
} 

// javac -encoding UTF-8 basic/VirtualThread/MatMulVT.java 
// java basic/VirtualThread/MatMulVT 10000

java传统线程池和虚拟线程仅仅是第29行和30行的区别。

试验结果

结果分析

  1. java虚拟线程确实比线程池快很多。
  2. go协程比java虚拟线程更轻,切换速度更快。
  3. go协程切换成本跟并发数保持了非常好的线性关系,并发数从1万->10万->100万,总耗时也是按10倍增长。

进阶内容

enjoy your golang travels! 如需快速、高效、深入的学习Go语言,欢迎试听我录制的golang视频课程《golang从入门到通天》(电脑端按Ctrl++放大页面观看)

Go 语言协程(Goroutine)和 Java线程(Thread)在很多方面有所不同。 首先,Goroutine 在调度和执行上更加高效。Go 语言的调度器可以在多个线程上运行数千个协程,并且自动处理调度和资源管理。与之相比,Java 线程的调度由 JVM 进行管理,创建和销毁线程的成本相对较高。 其次,Goroutine 的内存消耗较小。Goroutine 的栈大小通常只有几 KB,而 Java 线程默认的栈大小为几百 KB。这使得 Goroutine 创建和销毁的成本更低,可以更好地支持大规模并发。 此外,Goroutine 的通信机制更加简单和高效。Go 语言提供了轻量级的通道(Channel)用于协程之间的通信和同步。而在 Java 中,线程之间的通信需要使用底层的锁、条件变量等机制,编写复杂且容易出错。 然而,Java线程也有它的优势。Java 线程可以使用更多的底层系统资源,例如底层操作系统线程和多核 CPU 的并行计算。这使得 Java 线程适用于更加复杂和底层的系统级开发。而 Goroutine 主要用于高性能、高并发的轻量级并发编程。 最后,Goroutine 对于错误处理和防止资源泄漏有更好的支持。Go 语言的错误处理机制更加简洁和明确,并且协程的生命周期通常与错误处理机制紧密结合。在 Java 中,需要显式地进行异常处理,否则可能会导致资源无法释放或程序崩溃。 总之,Go 语言协程Java线程在调度、执行效率、资源消耗、通信机制等方面存在差异。选择使用哪种并发机制取决于具体的应用场景和需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值