Kotlin协程学习总结

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";
   }
 }
  • 通过Java Output字节码汇编比对, kotlin使用kotlin 1.3.50 compiler, Java使用OpenJDK 6u79


    image.png
  • Constant pool 比较,右图Kotlin,左图Java


    image.png
  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 修饰符标记的函数。
    它就像一个普通的函数类型,但具有 suspend 修饰符。
  • 挂起 lambda 表达式 : 必须在协程中运行的代码块。
  • 挂起函数类型 : 表示挂起函数以及挂起 lambda 表达式的函数类型,
    举个例子,suspend () -> Int 是一个没有参数、返回 Int 的挂起函数的函数类型。
    一个声明为 suspend fun foo() : Int 的挂起函数符合上述函数类型。
  • 协程构建器 : 使用一些挂起 lambda 表达式作为参数来创建一个协程的函数,并且可选地,还提供某种形式以访问协程的结果。
    例如,用例中的 launch{}、future{} 以及 sequence{} 就是协程构建器。
  • 挂起点 : 协程执行过程中可能被挂起的位置。
    从语法上说,挂起点是对一个挂起函数的调用,但实际的挂起在挂起函数调用了标准库中的原始挂起函数时发生。
  • 续体 : 是挂起的协程在挂起点时的状态.


    image.png

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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353