Kotlin协程学习总结

2020-06-23  本文已影响0人  James999

Kotlin VS JAVA

  1. 变量与常量
   //java
   String name = "Test";
   final String name = "Test";
  //kotlin
  var name = "Test"
  1. 空判断
 //java
if (text != null) {
    int length = text.length();
}
//kotlin
val length = text?.length()
  1. case 语句
//java
int score = // some score;
String grade;
switch (score) {
    case 9:
        grade = "Excellent";
        break;
    case 6:
        grade = "Good";
        break;
    case 5:
    case 4:
        grade = "OK";
        break;
    case 1:
        grade = "Fail";
        break;
    default:
        grade = "Fail";
}
// Kotlin
var score = // some score
var grade = when (score) {
    9, 10 -> "Excellent"
    in 6..8 -> "Good"
    4, 5 -> "OK"
    in 1..3 -> "Fail"
    else -> "Fail"
}
  1. 方法定义
// java
int getScore() {
   // logic here
   return score;
}
// Kotlin
fun getScore(): Int {
   // logic here
   return score
}
  1. 类,和接口的继承
// java
public class Child extends Parent implements IHome {
//
}
// kotlin
class Child : Parent(), IHome {
//
}
  1. java协程和kotlin线程
// java 线程
   void threadFun(){
        Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                   
                }
            });
        thread.start();
   }
// kotlin 协程
    fun coroutineFun() {
        GlobalScope.launch(Dispatchers.Main) {
            //delay(8000L) 
            println("CoroutineTest World!") 
        }
    }

Java VS Kotlin 生成 Bytecode

Java Kotlin编译过程对比

image.png

生成的Bytecode方式

  1. *.java code 由Javac 生成Bytecode
  2. *.kt code由 kotlinc-jvm

生成的 Bytecode 比对

Kotlin code 反编译回Java code的 汇编字节码对比。

  1. 如下Kotlin code Main.kt
class Main {
    fun  main() {
        val m = "Hello"
    }
}
  1. 选择菜单 Tools-> Kotlin-> show Kotlin Bytecode(在Kotlin Bytecode窗口右上方点击 Decompile)
    反编译后的code 如下
import kotlin.Metadata;
@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
   d2 = {"Lcom/yy/kotlindemo/kotlinbasic/Main;", "", "()V", "main", "", "app_debug"}
)
public final class Main {
   public final void main() {
      String m = "Hello";
   }
}

3.然后分别查看上述两段code的字节码

 public class Main {
   public void main() {
      String m = "Hello";
   }
 }
  1. Kotlin @Metadata 解析
Metadata KotlinClassHeader 相关描述
k kind 注解标注目标类型,例如类、文件等等
mv metadataVersion 该元数据的版本
bv bytecodeVersion 字节码版本
d1 data 自定义元数据
d2 strings 自定义元数据补充字段
xi extraInt 加入的附加标记,标记类文件的来源类型

简要说明Kotlin Class 文件结构

Main.class
Classfile /tmp/1405968806779468952/classes/Main.class
  Last modified May 28, 2020; size 566 bytes
  MD5 checksum b1d7f50abdea6527c5c67d5b34b227a0
  Compiled from "Main.kt"
public final class Main
SourceFile: "Main.kt"
  minor version: 0      // 副版本号
  major version: 50     //主版本号
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER //  flags 为一种掩码的访问标志,用于表示某个类,接口
  // ACC_PUBLIC: 声明为public,可以被包外访问,ACC_FINAL:声明为final,不允许有子类,[ACC_SUPER](https://blog.csdn.net/jokkkkk/article/details/86648610): 这个标志是为了纠正 invokespecial 在调用父类方法存在的版本问题。invokespecial是一个调用方法的字节码指令,用在调用构造方法、本类private方法、父类非虚方法3种场景
Constant pool:
   #1 = Utf8               Main
   #2 = Class              #1             // Main
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               main
   #6 = Utf8               ()V
   #7 = Utf8               Hello
   #8 = String             #7             // Hello
   #9 = Utf8               m
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8               this
  #12 = Utf8               LMain;
  #13 = Utf8               <init>
  #14 = NameAndType        #13:#6         // "<init>":()V
  #15 = Methodref          #4.#14         // java/lang/Object."<init>":()V
  #16 = Utf8               Lkotlin/Metadata;
  #17 = Utf8               mv
  #18 = Integer            1
  #19 = Integer            15
  #20 = Utf8               bv
  #21 = Integer            0
  #22 = Integer            3
  #23 = Utf8               k
  #24 = Utf8               d1
  #25 = Utf8               \u0000�\n���\n��\u0000\n��\n����\u00002�0�B�¢����J�����0�
  #26 = Utf8               d2
  #27 = Utf8
  #28 = Utf8               Main.kt
  #29 = Utf8               Code
  #30 = Utf8               LineNumberTable
  #31 = Utf8               LocalVariableTable
  #32 = Utf8               SourceFile
  #33 = Utf8               RuntimeVisibleAnnotations
{
 public Main();
    descriptor: ()V
    // 括号是空的,代表无参数 ,V 代表无返回值。
    // 如果是 descriptor: I  代表该变量的类型 是 int。 
    // 或者 descriptor: java.lang.string ?: 代表该变量的类型 是string。
    flags: ACC_PUBLIC  // flags: 代表该方法的修饰情况,ACC_PUBLIC 声明为public,可以从包外访问。
    Code:
      stack=1, locals=1, args_size=1
        start local 0 // Main this
         0: aload_0  // 从局部变量中加载索引为0的变量的值 (也指引用类型值)。 即this 的引用,压入栈 。
         1: invokespecial #15                 // Method java/lang/Object."<init>":()V
         // invokespecial 出栈,调用java/lang/Object."<init>":()V 初始化对象,就是this指定的对象的init()方法完成初始化。
         4: return
        end local 0 // Main this
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LMain;
  public final void main();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=1, locals=2, args_size=1
      // stack = 1 表示操作数栈深度为2;locals=2, 本地变量为2个;args_size=1 表示有一个参数(默认为this)
        start local 0 // Main this
         0: ldc           #8                  // String Hello
         // ldc 将String类型 “Hello” 常量值,从常量池 #8 推送至栈顶
         2: astore_1
         //将栈顶引用型数值存入第二个变量
        start local 1 // java.lang.String m
         3: return
        end local 0 // Main this
        end local 1 // java.lang.String m
      LineNumberTable:
      // LineNumberTable:指令与代码行数的偏移对应关系,每一行第一个数字对应代码行数,第二个数字对应前面code中指令
      // (注意: Javap -c中显示的东西叫做 指令。)
      // 也就是说Main.java文件中 第6行的代码 在执行顺序第 0 行。 Line 6: 就是第6行
        line 6: 0
        line 7: 3
      LocalVariableTable: 
      // 代表它是  局部变量表,start+length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头0
      // 到结尾10)slot就是这个变量在局部变量表中的槽位(槽位可复用),name是变量名称,Signatur是局部变量类型描述
        Start  Length  Slot  Name   Signature
            3       1     1     m   Ljava/lang/String;
            0       4     0  this   LMain;
}

RuntimeVisibleAnnotations:
  0: #16(#17=[I#18,I#18,I#19],#20=[I#18,I#21,I#22],#23=I#18,#24=[s#25],#26=[s#12,s#27,s#6,s#5,s#27])

Kotlin的协程在虚拟机上实现的原理

Kotlin 协程

  1. 默认情况,协程运行在一个共享的线程池中。 线程仍然存在于基于协程的程序中,但是一个线程可以运行大量的协程,所以这里不需要太多线程。
  2. 本质上,协程是轻量级的线程。 它们在某些 CoroutineScope 上下文中与 launch 协程构建器 一起启动。
    这里我们在 GlobalScope 中启动了一个新的协程,这意味着新协程的生命周期只受整个应用程序的生命周期限制。
import kotlinx.coroutines.*
fun main() {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

这是因为 delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。
delay 为什么不会造成线程阻塞。

  1. 协程术语

Suspend 挂起函数原理

挂起函数不会阻塞线程,其原理是kotlin编译层面的设计

Lambda 表达式

  1. Kotlin中 Lambda 表达式的约定。


    image.png

线程和协程

  1. 线程 线程拥有独立的栈、局部变量,基于进程的共享内存,因此数据共享比较容易,但是多线程时需要加锁来进行访问控制,不加锁就容易导致数据错误,但加锁过多又容易出现死锁。
    线程之间的调度由内核控制(时间片竞争机制),程序员无法介入控制。线程之间的切换需要深入到内核级别,因此线程的切换代价比较大,表现在:

    • 线程对象的创建和初始化
    • 线程上下文切换
    • 线程状态的切换由系统内核完成
    • 对变量的操作需要加锁
      image.png
  2. 协程 协程是跑在线程上的优化产物,被称为轻量级 Thread,拥有自己的栈内存和局部变量,共享成员变量。
    Coroutine 可以用来直接标记方法,由程序员自己实现切换,调度,不再采用传统的时间段竞争机制。
    在一个线程上可以同时跑多个协程,同一时间只有一个协程被执行,在单线程上模拟多线程并发,协程何时运行,
    何时暂停,都是有程序员自己决定的,使用: yield/resume API,优势如下:

    • 因为在同一个线程里,协程之间的切换不涉及线程上下文的切换和线程状态的改变,不存在资源、数据并发,所以不用加锁,只需要判断状态就OK,所以执行效率比多线程高很多。
    • 协程是非阻塞式的(也有阻塞API),一个协程在进入阻塞后不会阻塞当前线程,当前线程会去执行其他协程任务。
      image.png

参考

在线查看字节码
注解 Metedate 参考
jvms7.pdf
协程相关参考
[协程参考] (https://github.com/Kotlin-zh/KEEP/blob/master/proposals/coroutines.md)

上一篇下一篇

猜你喜欢

热点阅读