Java线程 VS Golang协程

本文不从语言角度谈论好与不好。本文从性能测试角度分析一下Java线程与Golang协程的区别

用例设计

java 实现多线程任务处理:启动一定数量的等待线程或空转线程,并让启动的线程维持固定时间(60秒)
golang实现多协程任务处理:启动一定数量的等待协程或空转协程,并让启动的协程维持固定时间(60秒)

测试结果

Java

在这里插入图片描述

Golang

在这里插入图片描述

结果分析

内存使用

Java线程的内存使用包括(约1Mb的虚拟内存 和 20Kb到60Kb的固定内存),空转状态的Java线程和sleep状态的Java线程在内存占用方面几乎无差别.
Golang协程的内存使用方面(不需要虚拟内存, 2Kb到4Kb的固定内存),空转状态的Golang协程和sleep状态的Golang协程在内存占用方面几乎无差别.

结论: Golang协程在内存开销方面比java线程有优势,开100w的Golang协程需占固定内存2.6G,虚拟内存2.6G。受限于内存大小没办法开100wjava线程

  • Java数据
条件虚拟内存消耗每个线程消耗虚拟内存固定内存消耗每个线程消耗固定内存
sleep线程(501-1)10592640Kb-10073528Kb500 * 1038.22Kb73420Kb-43312Kb500 * 60.216Kb
sleep线程(1001-501)11113744Kb-10592640Kb500 * 1042.20Kb84368Kb-73420Kb500 * 21.896Kb
sleep线程(1001-1)11113744Kb-10073528Kb1000 * 1040.21Kb84368Kb-43312Kb1000 * 41.056Kb
空转线程(101-1)10177000Kb-10073556Kb100 * 1034.44Kb46828Kb-43528Kb100 * 33.00Kb
空转线程(501-101)10592640Kb-10177000Kb400 * 1039.10Kb54484Kb-46828Kb400 * 19.14Kb
空转线程(501-1)10592640Kb-10073556Kb500 * 1038.16Kb54484Kb-43528Kb500 * 21.91Kb
  • Golang数据
条件虚拟内存消耗每个协程消耗虚拟内存固定内存消耗每个协程消耗固定内存问题
sleep协程(101-1)4976592Kb-4975812Kb100 * 7.80Kb2900Kb-2568Kb100 * 3.32Kb
sleep协程(501-1)4976592Kb-4975812Kb500 * 1.56Kb4108Kb-2568Kb500 * 3.08Kb
sleep协程(1001-501)4976592Kb-4976592Kb500 * 0Kb5452Kb-4108Kb500 * 2.69Kb
sleep协程(100w-1)7636288Kb-4975812Kb100w * 2.6Kb2613152Kb-2568Kb100w * 2.61Kb
空转协程(101-1)4975556Kb-4975556Kb100 * 0Kb2816Kb-2532Kb100 * 2.84Kb
空转协程(501-1)4975556Kb-4975556Kb500 * 0Kb3836Kb-2532Kb500 * 2.60Kb
空转协程(1001-501)4975812Kb-4975556Kb500 * 0.51Kb5208Kb-3836Kb500 * 2.74Kb
空转协程(100w-1)无参考意义无参考意义无参考意义无参考意义实际没有达到100w协程同时运行

系统线程消耗

每个java线程对应1个系统线程,Golang使用的系统线程数很少且不随协程数量变化
结论: Golang协程在系统线程使用方面有优势,如果开启过多的系统线程cpu要用大量的时间做线程切换反而降低了效率

  • Java数据
    在这里插入图片描述
  • Golang数据
    在这里插入图片描述

并发极限,执行效率

因单个协程占用的内存资源更少,所以机器能支持更多的Golang协程创建,所以在并发极限方面Golong 有优势
因减少了系统线程的切换让cpu专注于执行工作,所以Golang在执行效率方面有优势

实际场景

使用java多线程开发时
1 因频繁的线程切换会让整体执行效率降低,应尽量避免创建太多的线程。
2 因长时间占用cpu会让剩余的任务堆积等待,应该尽量避免单个线程长时间占用cpu。
3 使用线程池(享元模式)是一种很好的执行多任务的手段。

golang携程
golang 在网络IO中更具备优势,不过golang协程在某些方面也有问题
例如 磁盘IO是没实现poll方法的,不能用 epoll 池。所以磁盘io没有等待事件, Golang的Goroutine 会卡线程。如果OS 内核线程抽象Machine全部卡住,Go runtime 创建更多的线程来保证一直有可运行的 Machine。这种情况下也会造出像java一样的大量系统线程

展望

  • java什么时候也能有协程
    OpenJDK 的 JEP 425 :虚拟线程(预览版)功能提案显示:Java 平台将引入虚拟线程特性(期待已久的协程)
    有关虚拟线程的更多信息可在 OpenJDK 的 JDK Issue-8277131 中查看,目前该提案于 2021/11/15 创立,目前还处于 JEP 流程的第一阶段,距离稳定版本还需要一段时间。

测试机器机器信息

  • 机器年代 :2014
  • 操作系统 : MacOS
  • 内存 :16G / 1600MHZ / DDR3
    *CPU :两核 / Intel Core i5 / 2.6 GHz / L2缓存256KB / L3缓存3MB

测试代码

Java

package org.example;import com.google.common.base.Stopwatch;
import org.codehaus.plexus.util.StringUtils;
import org.junit.Test;import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class ThreadTest {enum ThreadType {RUN_THREAD,SLEEP_THREAD}static class RunThread extends Thread {private final long runtime;public RunThread(long runtime) {this.runtime = runtime;}@Overridepublic void run() {long startTime = System.currentTimeMillis();long endTime = startTime + runtime;while (System.currentTimeMillis() < endTime) {}}}static class SleepThread extends Thread {private final long runtime;public SleepThread(long runtime) {this.runtime = runtime;}@Overridepublic void run() {try {Thread.sleep(runtime);} catch (InterruptedException e) {e.printStackTrace();}}}public static void runHoldThread(ThreadType threadType, long holdTime, int number) {long startTime = System.currentTimeMillis();System.out.println("start");Thread[] threadArray = new Thread[number];for (int i = 0; i < number; i++) {switch (threadType) {case RUN_THREAD:threadArray[i] = new RunThread(holdTime);break;case SLEEP_THREAD:threadArray[i] = new SleepThread(holdTime);break;}}System.out.println("create complete");long createThreadOverTime = System.currentTimeMillis();for (Thread thread : threadArray) {thread.start();}System.out.println("all started");long callOverTime = System.currentTimeMillis();for (Thread thread : threadArray) {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}}long endTime = System.currentTimeMillis();System.out.printf("调用 耗时:%d ms , 等待耗时: %d ms ,共耗时 %d ms", callOverTime - createThreadOverTime, endTime - callOverTime, endTime-startTime);}@Testpublic void threadTest() {String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];System.out.printf("MAC 查看进程命令:\nps aux -M %s\ntop -pid %s\n", pid, pid);//        runHoldThread(ThreadType.SLEEP_THREAD, 60000, 1);
//        runHoldThread(ThreadType.SLEEP_THREAD, 60000, 101);
//        runHoldThread(ThreadType.SLEEP_THREAD, 60000, 501);
//        runHoldThread(ThreadType.SLEEP_THREAD, 60000, 1001);
//        runHoldThread(ThreadType.RUN_THREAD, 60000, 1);
//        runHoldThread(ThreadType.RUN_THREAD, 60000, 101);
//        runHoldThread(ThreadType.RUN_THREAD, 60000, 501);runHoldThread(ThreadType.RUN_THREAD, 60000, 1001);}
}

Golang

package gmpimport ("fmt""os""sync""testing""time"
)type RoutineType stringconst (RunRoutine   RoutineType = "RunRoutine"SleepRoutine RoutineType = "SleepRoutine"
)func TestGoroutine(t *testing.T) {pid := os.Getpid()fmt.Printf("MAC 查看进程命令:\nps aux -M %d\ntop -pid %d\n", pid, pid)//创建 n 个协程,每个空转 m秒//runGoroutine(SleepRoutine, time.Second*60, 1)//runGoroutine(SleepRoutine, time.Second*60, 101)//runGoroutine(SleepRoutine, time.Second*60, 501)//runGoroutine(SleepRoutine, time.Second*60, 1001)//runGoroutine(SleepRoutine, time.Second*60, 1000001)//runGoroutine(RunRoutine, time.Second*60, 1)//runGoroutine(RunRoutine, time.Second*60, 101)//runGoroutine(RunRoutine, time.Second*60, 501)//runGoroutine(RunRoutine, time.Second*60, 1001)runGoroutine(RunRoutine, time.Second*60, 1000001)
}//运行协程
func runGoroutine(routineType RoutineType, singleRunningTime time.Duration, number int) {startTime := time.Now()var waitGroup sync.WaitGroupfor i := 0; i < number; i++ {waitGroup.Add(1)switch routineType {case RunRoutine:go runRoutine(singleRunningTime, &waitGroup)case SleepRoutine:go sleepRoutine(singleRunningTime, &waitGroup)}}callOverTime := time.Now()waitGroup.Wait()runOverTime := time.Now()callDuration := callOverTime.Sub(startTime).Milliseconds()waitDuration := runOverTime.Sub(callOverTime).Milliseconds()fmt.Printf("调用 耗时:%d ms , 等待耗时: %d ms", callDuration, waitDuration)
}//空转协程
func runRoutine(runningTime time.Duration, waitGroup *sync.WaitGroup) {defer waitGroup.Done()timeStart := time.Now()endTime := timeStart.Add(runningTime)for time.Now().Before(endTime) {}
}//sleep协程
func sleepRoutine(runningTime time.Duration, waitGroup *sync.WaitGroup) {defer waitGroup.Done()time.Sleep(runningTime)
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部