协程的性能对比

前言

Java 21里正式发布了java的协程(Java17作为预览功能有提供),内部叫虚拟线程,参考:https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-15BDB995-028A-45A7-B6E2-9BA15C2E0501 本文是Java协程和线程调用的简单性能对比,以及GO协程的简单介绍。

原理

下面直接对Java虚拟线程直接叫协程,关键原理:

  • Java协程的调度在用户空间进行,而不依赖操作系统内核,它的创建销毁和上下文切换的开销更低,且多个协程映射到少量的平台线程(操作系统的线程)上。

  • 采用协作式多任务模型。当一个协程阻塞在 I/O 操作或其他等待状态时,它会主动释放执行权,允许其他协程在同一个平台线程上运行,而不是像传统线程那样被操作系统强制挂起。

GO语言主要优势是高性能高并发,一个关键点是提供了 goroutine的协程并发能力,基于GMP调度模型实现了协程能力:

• G:表示goroutine,goroutine是Go语言中的协程。

• M:抽象了内核线程,代表操作系统的线程,用于执行goroutines,当goroutine 调度到线程时,使用该goroutine 自己的栈信息。

• P:代表处理器,负责调度goroutine,每个P维护一个本地goroutine 队列,M 从P 上获得goroutine 并执行。P还负责从全局队列中获取新的任务。

性能对比

下面直接看代码执行结果。

JAVA比较协程和线程循环1万次方法,每次方法内部休眠500毫秒的调用:

public class VirtualThreadTest {

    static List<Integer> list = new ArrayList<>();
    static int size = 10000;

    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
            ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
            updateMaxThreadNum(threadInfo.length);
        }, 10, 10, TimeUnit.MILLISECONDS);

        testVirtualThread();
    }

    public static void testVirtualThread() throws InterruptedException {
        list = new ArrayList<>();
        Thread.sleep(200);
        System.out.println("start:" + list.get(0) + " platform threads");

        long start = System.currentTimeMillis();
        // 1.虚拟线程
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        // 2.平台线程
        // ExecutorService executor =  Executors.newFixedThreadPool(500);

        CountDownLatch countDownLatch = new CountDownLatch(size);
        for (int i = 0; i < size; i++) {
            executor.submit(() -> {
                try {
                    // 睡眠500毫秒
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException ex) {
                    System.out.println(ex);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        executor.close();
        System.out.println("max:" + list.get(0) + " platform threads");
        System.out.printf("cost:%dms\n", System.currentTimeMillis() - start);
    }

    private static void updateMaxThreadNum(int num) {
        if (list.isEmpty()) {
            list.add(num);
        } else {
            Integer integer = list.get(0);
            if (num > integer) {
                list.add(0, num);
            }
        }
    }

}
用协程循环1万次,日志:
start:9 platform threads
max:14 platform threads
cost:1210ms
1秒多实际只用了5个线程就结束了。

改成2.平台线程,普通500固定线程池1万次,日志:
start:9 platform threads
max:509 platform threads
cost:10541ms
10秒多用了500个线程才结束。

另参考GO协程(GO协程只需方法前加go即可)循环1万次方法,每次方法内部休眠500毫秒的调用:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var waitGroup sync.WaitGroup

    start := time.Now()
    size := 10000
    waitGroup.Add(size)
    for i := 0; i < size; i++ {
        go func() {
            defer waitGroup.Done()
            time.Sleep(500 * time.Millisecond)
        }()
    }

    waitGroup.Wait()
    fmt.Println("cost:", time.Now().Sub(start).Milliseconds())
}
运行:go run 122401.go
cost: 558
500多毫秒1万次循环

通过上面的简单对比,可以看出用协程性能明显更优,由于对GO还不够熟,GO协程如有问题欢迎沟通。

Java21前,如用协程可使用非官网第三方库实现,比如quasar:co.paralleluniverse.fibers.Fiber 详情参考其他资料。如要升级Java到Java21,涉及太多中间件的兼容性问题,所以要升级还是得慎重分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值