本文不从语言角度谈论好与不好。本文从性能测试角度分析一下Java线程与Golang协程的区别
用例设计
javagolang 
测试结果
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-10073528Kb | 500 * 1038.22Kb | 73420Kb-43312Kb | 500 * 60.216Kb | 
| sleep线程(1001-501) | 11113744Kb-10592640Kb | 500 * 1042.20Kb | 84368Kb-73420Kb | 500 * 21.896Kb | 
| sleep线程(1001-1) | 11113744Kb-10073528Kb | 1000 * 1040.21Kb | 84368Kb-43312Kb | 1000 * 41.056Kb | 
| 空转线程(101-1) | 10177000Kb-10073556Kb | 100 * 1034.44Kb | 46828Kb-43528Kb | 100 * 33.00Kb | 
| 空转线程(501-101) | 10592640Kb-10177000Kb | 400 * 1039.10Kb | 54484Kb-46828Kb | 400 * 19.14Kb | 
| 空转线程(501-1) | 10592640Kb-10073556Kb | 500 * 1038.16Kb | 54484Kb-43528Kb | 500 * 21.91Kb | 
- Golang数据
 
| 条件 | 虚拟内存消耗 | 每个协程消耗虚拟内存 | 固定内存消耗 | 每个协程消耗固定内存 | 问题 | 
|---|---|---|---|---|---|
| sleep协程(101-1) | 4976592Kb-4975812Kb | 100 * 7.80Kb | 2900Kb-2568Kb | 100 * 3.32Kb | 无 | 
| sleep协程(501-1) | 4976592Kb-4975812Kb | 500 * 1.56Kb | 4108Kb-2568Kb | 500 * 3.08Kb | 无 | 
| sleep协程(1001-501) | 4976592Kb-4976592Kb | 500 * 0Kb | 5452Kb-4108Kb | 500 * 2.69Kb | 无 | 
| sleep协程(100w-1) | 7636288Kb-4975812Kb | 100w * 2.6Kb | 2613152Kb-2568Kb | 100w * 2.61Kb | 无 | 
| 空转协程(101-1) | 4975556Kb-4975556Kb | 100 * 0Kb | 2816Kb-2532Kb | 100 * 2.84Kb | 无 | 
| 空转协程(501-1) | 4975556Kb-4975556Kb | 500 * 0Kb | 3836Kb-2532Kb | 500 * 2.60Kb | 无 | 
| 空转协程(1001-501) | 4975812Kb-4975556Kb | 500 * 0.51Kb | 5208Kb-3836Kb | 500 * 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;
        }
        @Override
        public 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;
        }
        @Override
        public 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);
    }
    @Test
    public 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 gmp
import (
   "fmt"
   "os"
   "sync"
   "testing"
   "time"
)
type RoutineType string
const (
   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.WaitGroup
   for 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)
}