9.4 协程是轻量级的

直接运行下面的代码:

  1. fun testThread() {
  2. val jobs = List(100_1000) {
  3. Thread({
  4. Thread.sleep(1000L)
  5. print(".")
  6. })
  7. }
  8. jobs.forEach { it.start() }
  9. jobs.forEach { it.join() }
  10. }

我们应该会看到输出报错:

  1. Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
  2. at java.lang.Thread.start0(Native Method)
  3. at java.lang.Thread.start(Thread.java:714)
  4. at com.easy.kotlin.LightWeightCoroutinesDemo.testThread(LightWeightCoroutinesDemo.kt:30)
  5. at com.easy.kotlin.LightWeightCoroutinesDemoKt.main(LightWeightCoroutinesDemo.kt:40)
  6. ...........................................................................................

我们这里直接启动了100,000个线程,并join到一起打印”.”, 不出意外的我们收到了 java.lang.OutOfMemoryError

这个异常问题本质原因是我们创建了太多的线程,而能创建的线程数是有限制的,导致了异常的发生。在Java中, 当我们创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。 能创建的线程数的具体计算公式如下:

Number of Threads = (MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize)

其中,参数说明如下:

参数 说明
MaxProcessMemory 指的是一个进程的最大内存
JVMMemory JVM内存
ReservedOsMemory 保留的操作系统内存
ThreadStackSize 线程栈的大小

我们通常在优化这种问题的时候,要么是采用减小thread stack的大小的方法,要么是采用减小heap或permgen初始分配的大小方法等方式来临时解决问题。

在协程中,情况完全就不一样了。我们看一下实现上面的逻辑的协程代码:

  1. fun testLightWeightCoroutine() = runBlocking {
  2. val jobs = List(100_000) {
  3. // create a lot of coroutines and list their jobs
  4. launch(CommonPool) {
  5. delay(1000L)
  6. print(".")
  7. }
  8. }
  9. jobs.forEach { it.join() } // wait for all jobs to complete
  10. }

运行上面的代码,我们将看到输出:

  1. START: 21:22:28.913
  2. .....................
  3. .....................(100000个)
  4. .....END: 21:22:30.956

上面的程序在2s左右的时间内正确执行完毕。