主题
第 1 章 Kotlin 是什么 & 为什么这么火
学习目标:能用一句话向产品经理讲清楚「Kotlin 到底是个什么玩意儿」;能向面试官讲清楚「为啥 Google 要钦定 Kotlin 而不是继续用 Java」;理解 Kotlin 三大杀手锏 —— 空安全、扩展函数、协程 —— 各自解决了什么痛点。
1.1 一段「家谱」:Kotlin 从哪来
1.1.1 编程语言简史(30 秒版本)
1995 ── Java 1.0 (Sun Microsystems) ┐
2000 ── C# 1.0 (Microsoft 仿 Java) │
2005 ── Scala 1.0 (函数式 + 面向对象) │ Java 的"竞品"陆续登场
2007 ── Clojure 1.0 (Lisp on JVM) │
2009 ── Go 1.0 (Google) │
2011 ── ★ Kotlin 项目启动 (JetBrains) ┘
2016 ── Kotlin 1.0 正式发布
2017 ── 🎉 Google I/O 宣布 Kotlin 为 Android 一等公民
2019 ── Google 进一步宣布 "Kotlin First"(新 API 优先 Kotlin 写)
2020 ── kotlinx.coroutines 1.4 协程稳定
2023 ── Kotlin 1.9,K2 编译器进入 Beta
2024 ── Kotlin 2.0 发布,K2 编译器正式版(编译速度提升 ~2 倍)1.1.2 为什么 JetBrains 要造一门新语言?
故事很有意思。JetBrains 是写 IntelliJ IDEA 的那家公司,他们自己内部用 Java 写产品,写得头很大,痛点 4 条:
┌────────────────────────────────────────────────────────┐
│ Java 的 4 个痛 │
├────────────────────────────────────────────────────────┤
│ 1. NullPointerException 满天飞,号称"十亿美元错误" │
│ 2. 一个简单的 POJO 要写 50 行 getter/setter/equals │
│ 3. 缺少函数式 API(Java 8 才有 Stream,太晚了) │
│ 4. 异步代码靠回调地狱(Thread/Future/CompletableFuture) │
└────────────────────────────────────────────────────────┘他们调研了一圈:
- Scala:太复杂了,编译慢得离谱(团队"看着进度条上厕所")
- Groovy:动态类型,IDE 自动补全的体验差
- Clojure:Lisp 风格,团队学习曲线太陡
- C#:好是好,但被 .NET 绑死
最终结论是 "那不如自己造一个"。2010 年立项,2011 年公开命名为 Kotlin(取自俄罗斯圣彼得堡附近的一座岛 Kotlin Island —— 跟 Java = 印尼爪哇岛对应)。
1.1.3 Google 为什么钦定它?
2017 年 Google I/O 大会震惊业界 —— 安卓官方第一语言不再独尊 Java,Kotlin 成为一等公民。这背后有两个原因:
原因 1:Oracle 起诉 Google 用 Java API 侵权
──────────────────────────────────────────────
2010 年 Oracle 收购 Sun(即 Java 的所有者),随即起诉 Google
"Android 用 Java API 没付授权费"。这场官司打了 11 年(直到
2021 年 Google 才胜诉)。Google 急需一个"Plan B"语言。
原因 2:Java 在 Android 上更新太慢
──────────────────────────────────────────────
Android 长期卡在 Java 7 / Java 8 部分特性,没法用现代 Java。
而 Kotlin 由 JetBrains 维护,可以快速演进、立即可用。于是 Kotlin 一举成为「Android 官方推荐 + JVM 后端可用 + 跨平台 KMP」的多面手。
1.2 Kotlin 是什么:一句话讲清
Kotlin 是一门运行在 JVM 上的、静态类型的、跨平台的、现代化的、跟 Java 100% 互操作的通用编程语言。
这句话信息量很大,逐词拆开看:
| 词 | 含义 | 通俗类比 |
|---|---|---|
| 运行在 JVM 上 | 编译成 .class 字节码 | 跟 Java 共用同一台"虚拟机",所以能复用整个 Java 生态 |
| 静态类型 | 编译期就检查类型 | 写代码时 IDE 能秒标红错误,不像 Python 要等运行时 |
| 跨平台 | JVM / Android / JS / Native / Wasm | 同一套代码可以跑在多个平台 |
| 现代化 | 函数式 + 面向对象 + null 安全 + 协程 | Java 的"豪华增强版" |
| 跟 Java 互操作 | Kotlin 调 Java 不用任何转换;反之亦然 | 老项目可以一行行渐进迁移 |
| 通用编程语言 | 不限领域 | 后端、Android、CLI、脚本、数据处理都能写 |
1.2.1 它是怎么跑起来的?
┌──────────────┐ kotlinc ┌──────────────┐
│ .kt 源码 │ ───────────────► │ .class 字节码 │
└──────────────┘ (Kotlin 编译器) └───────┬──────┘
│
JVM ▼
┌──────────────┐
│ 运行时执行 │
└──────────────┘
Kotlin Multiplatform 的另两条腿:
.kt ─► Kotlin/JS ─► .js ─► 浏览器
.kt ─► Kotlin/Native ─► .exe ─► macOS / Linux / iOS / Windows关键事实:Kotlin 编译出来的 .class 文件跟 Java 编译出来的 .class 文件长得几乎一样,运行时它们就是同一种字节码,所以"互操作"是天然的,不是后期勉强适配的。
1.3 Kotlin 三大杀手锏
讲了那么多背景,我们来看 Kotlin 真正"打动人心"的 3 个特性。这也是面试官最爱问的"为什么 Kotlin 比 Java 好"。
1.3.1 杀手锏 1:Null 安全 —— 干掉 NPE
痛点:Java 的 NullPointerException
java
// Java 代码 —— 看起来人畜无害
String name = user.getProfile().getName();
System.out.println(name.length());
// 运行时如果 user 是 null 或 profile 是 null,就 💥 NullPointerException
// 这种 bug 编译期完全发现不了,全靠测试 + 线上炸 + 哭Kotlin 怎么救你
Kotlin 把「这个值会不会是 null」写进了类型系统:
kotlin
// 这样写 —— 编译器拒绝传 null 给 name
val name: String = "Alice"
// name = null // ❌ 编译报错
// 想要可空?类型后面加个问号
val nullableName: String? = null // ✅ OK,类型变成 String?
// 用可空类型时,编译器强制你处理 null 的情况
val length = nullableName.length // ❌ 编译报错:你不能直接点
val length = nullableName?.length // ✅ 安全调用:null 时返回 null
val length = nullableName?.length ?: 0 // ✅ Elvis 操作符:null 时给默认值
val length = nullableName!!.length // ⚠️ 强制非空:null 时抛异常(自杀式)📌 生活化类比:Java 的所有变量都是「可能没装东西的快递盒」,每次拆都要心惊胆战;Kotlin 的盒子分两种 —— 保证装了东西的(
String) 和 可能没装的(String?),盒子上贴了标签,拆错了编译期就报错,不用等运行时翻车。
1.3.2 杀手锏 2:扩展函数 —— 给别人的类加方法
痛点:Java 的 StringUtils.isBlank()
java
// Java —— 想给 String 加个 toCamelCase() 方法?
// 类是 java.lang.String,你又改不了,只能写一个工具类:
public class StringUtils {
public static String toCamelCase(String s) { /* ... */ }
}
// 调用起来像这样,丑得要死:
String result = StringUtils.toCamelCase(input);Kotlin 怎么救你
kotlin
// 直接给 String 类"扩展"一个方法
fun String.toCamelCase(): String {
return this.split("_").joinToString("") { it.replaceFirstChar { c -> c.uppercase() } }
}
// 调用起来跟内置方法一模一样:
val result = "hello_kotlin_world".toCamelCase() // → "HelloKotlinWorld"📌 生活化类比:Java 让你"想给别人家的房子加阳台,只能在自家院子搭个铁架子";Kotlin 让你直接"在别人家房子上加个看起来像原装的阳台",路过的人看不出区别。
1.3.3 杀手锏 3:协程 —— 写同步代码,跑出异步效果
痛点:Java 的回调地狱
java
// Java 异步调用 —— 一层一层套回调
fetchUser(userId, user -> {
fetchOrders(user.getId(), orders -> {
fetchPayments(orders.get(0).getId(), payments -> {
// 缩进已经爆炸,再嵌套一层就爬不出来了
});
});
});Kotlin 怎么救你
kotlin
// 看起来跟同步代码一样,实际是非阻塞的
suspend fun loadUserData(userId: String) {
val user = fetchUser(userId) // 不阻塞线程
val orders = fetchOrders(user.id) // 不阻塞线程
val payments = fetchPayments(orders[0].id) // 不阻塞线程
println(payments)
}📌 生活化类比:传统线程像「每来 1 个客人,餐厅就雇 1 个服务员盯着他」,客人多了服务员不够用;协程像「1 个服务员同时看 100 桌,谁举手就过去」,因为大部分时间客人都在自己吃菜(IO 等待中)。
1.4 Kotlin vs Java 全方位对比
| 维度 | Java | Kotlin | 谁赢 |
|---|---|---|---|
| Null 安全 | NPE 满天飞 | 类型系统保证 | Kotlin 大胜 |
| 数据类 | 50 行模板 + Lombok 凑活 | data class 一行 | Kotlin 大胜 |
| 函数式 | Stream API(Java 8+) | 内置完整集合操作 | Kotlin 略胜 |
| 异步 | CompletableFuture / 虚拟线程 | 协程 | Kotlin 略胜 |
| 类型推断 | var(Java 10+,仅局部) | val/var 全场景 | Kotlin 略胜 |
| 编译速度 | 快 | 略慢(K2 后已追平) | Java 略胜 |
| 生态成熟度 | 30 年历史 | 10 年历史 | Java 大胜 |
| 学习曲线 | 简单粗暴 | 特性多但设计自洽 | 平手 |
| 面试题数量 | 海量 | 中等 | Java 多 |
| 招聘市场 | 后端主流 | Android 主流 + 后端在涨 | 看方向 |
1.4.1 一段代码看尽差距
需求:定义一个用户类,包含 ID、姓名、年龄;要支持 equals / hashCode / toString。
Java 版本(不用 Lombok):
java
public class User {
private final String id;
private final String name;
private final int age;
public User(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() { return id; }
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(id, user.id) &&
Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
@Override
public String toString() {
return "User{id='" + id + "', name='" + name + "', age=" + age + '}';
}
}Kotlin 版本:
kotlin
data class User(val id: String, val name: String, val age: Int)同一个类,Java 38 行,Kotlin 1 行。这就是为什么很多人转 Kotlin 之后回不去 Java 了。
1.5 Kotlin 适用场景全景图
★ Kotlin 全场景能力雷达 ★
Android 开发 ⭐⭐⭐⭐⭐
│
服务端 │ 工具脚本
⭐⭐⭐⭐ ─────┼──── ⭐⭐⭐
│
───────── │ ─────────
│
数据科学 │ 跨平台 KMP
⭐⭐⭐ │ ⭐⭐⭐⭐
│
前端(KJS)
⭐⭐1.5.1 主战场 1:Android 开发
- 语言地位:Google 官方"Kotlin First",新 Jetpack 库优先 Kotlin API
- 配套:Jetpack Compose(声明式 UI)、Coroutines、Flow、Room、Hilt
- 就业:Android 招聘 90% 要求 Kotlin
1.5.2 主战场 2:JVM 后端
- 框架:Spring Boot 原生支持 Kotlin;Ktor(JetBrains 自家轻量框架)
- 优势:协程 + 数据类 + 不可变集合,写微服务非常顺手
- 大厂案例:京东、字节、Pinterest、Atlassian
1.5.3 跨界场景:Kotlin Multiplatform (KMP)
- 理念:iOS / Android / Web / Server 共享业务逻辑代码
- 现状:2023 年 KMP 已稳定,Netflix、Philips、McDonald's 在用
- 限制:UI 层一般还是各端原生,业务逻辑层共享
1.5.4 小众但好用:Gradle 构建脚本
- 把
build.gradle(Groovy)换成build.gradle.kts(Kotlin) - IDE 有完整类型提示,再也不用对着 Groovy 文档"猜方法名"
1.6 学 Kotlin 之前你需要知道的 5 件事
1.6.1 你需要 Java 基础吗?
不严格需要,但有的话快很多。Kotlin 几乎所有概念在 Java 里都能找到对应:
| Kotlin 概念 | Java 对应 |
|---|---|
val | final 变量 |
class | class |
interface | interface |
Int / String | int / String |
| 包 / import | 包 / import |
完全没基础也没关系,本笔记不假设你会 Java。
1.6.2 Kotlin 文件长啥样?
kotlin
// 文件名:Hello.kt
package com.example.hello // 包名(可选)
import kotlin.math.PI // 导入(按需)
// 顶层函数(不用写 class)
fun main() {
println("Hello, Kotlin!")
println("π ≈ $PI")
}📌 跟 Java 不同的一点:Java 里"什么都得放在 class 里",Kotlin 允许函数和变量直接在文件顶层 —— 写小工具脚本特别方便。
1.6.3 Kotlin 文件的 3 种典型用法
┌─────────────────────────────────────────────────────────┐
│ 1. .kt 文件 - 普通源代码 │
│ 2. .kts 文件 - Kotlin 脚本(无需编译,直接 kotlinc 跑)│
│ 3. .gradle.kts 文件 - Gradle 构建脚本 │
└─────────────────────────────────────────────────────────┘1.6.4 Kotlin 跟 Android 的关系
Kotlin ≠ Android。Kotlin 是一门通用语言,可以脱离 Android 单独跑。但在中文圈"Kotlin"这个词常常被默认是"安卓 Kotlin",造成误解。本笔记只讲 Kotlin 语言本身,不涉及 Android 框架。
1.6.5 学完后你需要继续学的方向
┌──────────────────────────────────────────────────────────┐
│ 方向 A:Android → Jetpack Compose + Coroutines + Hilt │
│ 方向 B:后端 → Ktor / Spring Boot + Exposed │
│ 方向 C:跨平台 → Kotlin Multiplatform (KMP) │
│ 方向 D:DSL → 阅读 Gradle Kotlin DSL / Kotest 源码 │
└──────────────────────────────────────────────────────────┘1.7 章末小结
记住这张图,第 1 章你就毕业了:
★ 第 1 章核心知识图谱 ★
│
┌─────────────────────────┼──────────────────────────┐
│ │ │
┌────▼─────┐ ┌────▼──────┐ ┌────▼────┐
│ 是什么 │ │ 为什么火 │ │ 适用场景 │
├──────────┤ ├───────────┤ ├─────────┤
│ JVM 语言 │ │ Null 安全 │ │ Android │
│ 静态类型 │ │ 扩展函数 │ │ 后端 │
│ 跨平台 │ │ 协程 │ │ KMP │
│ Java 互操│ │ 简洁语法 │ │ 脚本 │
└──────────┘ └───────────┘ └─────────┘🎤 1.8 章末面试题(10 道高频题)
Q1. Kotlin 跟 Java 是什么关系?编译后是什么?
答:Kotlin 是 JetBrains 开发的运行在 JVM 上的语言,编译产物是 Java 字节码(.class)。它跟 Java 100% 互操作 —— Kotlin 可以直接调 Java 类,反过来 Java 也能调 Kotlin 类。两者编译出来后在 JVM 看来是一样的字节码。
Q2. Google 为什么把 Kotlin 升为 Android 官方语言?
答:3 个主要原因:
- Oracle 起诉 Java API 侵权,Google 急需一个不受 Oracle 控制的 Plan B 语言;
- Android 长期卡在 Java 7 / Java 8 子集,无法跟上 Java 现代特性,而 Kotlin 由 JetBrains 自主演进,可以立即用上现代特性;
- Kotlin 解决了 Java 的几大痛点(NPE、模板代码、缺少协程),开发体验显著提升。
Q3. Kotlin 解决了 Java 的哪些核心痛点?
答:
| 痛点 | Java 现状 | Kotlin 解法 |
|---|---|---|
| NullPointerException | 无类型保护 | 类型系统区分 T 和 T? |
| 模板代码多 | POJO 50 行 / Lombok 凑活 | data class 一行 |
| 异步代码难写 | 回调地狱 / CompletableFuture | 协程(写同步形式) |
| 函数式 API 弱 | Java 8 才有 Stream | 集合 + 序列原生支持 |
| 工具方法别扭 | 静态工具类 StringUtils.foo(s) | 扩展函数 s.foo() |
Q4. val 和 var 有什么区别?
答:
val= value,只读引用(类似 Java 的final)—— 一旦初始化,引用本身不能换,但引用的对象内部状态可以变(比如val list = mutableListOf<Int>()后还能list.add(1))。var= variable,可变引用,可以重新赋值。
💡 最佳实践:默认全部用
val,只有真的需要重新赋值才用var。这能避免大量隐藏 bug。
Q5. Kotlin 是怎么实现"空安全"的?
答:核心是把"是否可空"写进类型本身。
String是非空类型,编译器禁止给它赋null;String?是可空类型,可以是null,但访问它的方法时编译器强制你处理 null 的情况(用?.、?:、!!或if (x != null)智能转换)。
这等于把"运行时炸"提前到了"编译时报错",从源头消除了 NPE。
Q6. Kotlin 跟 Java 互操作时,有什么需要注意的"坑"?
答:最大的坑是 平台类型(Platform Type)。Kotlin 调 Java 时,Java 类型系统不区分可空非空(除非 Java 方法上加了 @Nullable/@NotNull 注解),Kotlin 编译器把这种类型标为 String!(带感叹号),既能当非空也能当可空 —— 写错了运行时仍可能 NPE。
💡 解决方法:调 Java API 时尽量声明明确的可空性(
val name: String? = javaApi.getName()),不要图省事。
Q7. Kotlin 适合做什么?不适合做什么?
答:
- 适合:Android 开发、JVM 后端服务(Spring/Ktor)、Gradle 脚本、跨平台 KMP、CLI 工具
- 不适合:
- 系统编程(Kotlin/Native 当前生态弱于 Rust/Go)
- 高性能计算(GC + JVM 启动开销)
- 数据科学(Python 生态碾压)
- 游戏引擎开发(C++/C# 主流)
Q8. Kotlin 协程跟 Java 线程有什么区别?
答:
| 维度 | Java Thread | Kotlin 协程 |
|---|---|---|
| 本质 | 操作系统线程 | 用户态调度的"轻量任务" |
| 创建成本 | 1MB 栈 / 个 | 几 KB / 个 |
| 数量上限 | 几千 | 几百万 |
| 阻塞代价 | 占用一个 OS 线程 | suspend 时让出线程 |
| 切换 | 内核态调度 | 用户态调度(编译器协作) |
简单说:协程是"写起来像同步、跑起来像异步"的语法糖 + 状态机。
⚠️ 注意:Java 21 之后引入了"虚拟线程"(Project Loom),思想跟协程类似,所以未来差距会缩小。
Q9. Kotlin 编译产物跟 Java 完全一样吗?
答:几乎一样,但不完全一样。
- Kotlin 编译出来也是 JVM 字节码,跑在同一个 JVM 上;
- 但 Kotlin 编译器会额外生成一些辅助类(如
Kotlin Metadata注解),用来在反射时还原 Kotlin 特有信息(如data class的属性、可空性); - 协程会被编译成状态机字节码(CPS 变换),看起来跟同步代码字节码完全不同。
所以从 Java 反编译 Kotlin 代码会看到一些"奇怪"的辅助类和 Continuation 参数。
Q10. 如果你已经会 Java,学 Kotlin 大概要多久?
答:
- 基础语法(变量、控制流、类、函数):1 天
- 流畅写代码(含 Lambda / 集合 API / 数据类):1 周
- 写出"地道 Kotlin"(idiomatic):1 个月
- 协程精通(含 Flow / 异常处理 / 取消):3 个月
💡 关键点:会写 Kotlin 不等于会"地道 Kotlin"。前期容易"用 Java 思维写 Kotlin",比如该用
let的地方还在写if (x != null)。靠多读优秀开源项目能加速这个过程。
下一章 → 第 2 章 · 环境搭建 & 第一行 Kotlin →
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗
💻 示例代码
kotlin
/**
* 第 1 章 · Kotlin 是什么 — 第一段示例
*
* 这段代码体现了 Kotlin 的几个特点,对照注释能快速感受跟 Java 的差别。
*
* 运行方式(在装好 Kotlin CLI 之后):
* kotlinc HelloKotlin.kt -include-runtime -d hello.jar
* java -jar hello.jar
*
* 也可以直接复制到 https://play.kotlinlang.org/ 在线运行。
*/
// ---------- 1. 顶层函数:不用包在 class 里 ----------
fun main() {
println("Hello, Kotlin!")
// ---------- 2. 变量:val 只读 / var 可变 ----------
val name: String = "Alice" // 显式类型
val age = 25 // 自动推断为 Int
var counter = 0 // var 才能后续重新赋值
counter += 1
// ---------- 3. 字符串模板:$ 直接插值 ----------
println("$name, $age 岁,counter = $counter")
println("一年后她 ${age + 1} 岁了")
// ---------- 4. Null 安全:类型系统区分可空与不可空 ----------
val nickname: String? = null // 注意问号
val length = nickname?.length ?: 0 // ?. 安全调用 + ?: Elvis 默认值
println("昵称长度 = $length")
// ---------- 5. 集合 + 函数式操作 ----------
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val doubled = numbers
.filter { it % 2 == 0 } // 只留偶数
.map { it * 2 } // 每个 ×2
println("偶数翻倍: $doubled") // → [4, 8, 12, 16, 20]
// ---------- 6. data class:一行定义包含 equals / hashCode / toString 的类 ----------
data class User(val id: String, val name: String, val age: Int)
val u1 = User("u001", "Alice", 25)
val u2 = User("u001", "Alice", 25)
println("u1 == u2 ? ${u1 == u2}") // true(结构相等)
println(u1) // User(id=u001, name=Alice, age=25)
// copy 一个改了年龄的副本,常用于不可变数据更新
val u3 = u1.copy(age = 26)
println("u3 = $u3")
// ---------- 7. 扩展函数:给已有类加方法 ----------
fun String.shout(): String = this.uppercase() + "!"
println("hello".shout()) // → HELLO!
// ---------- 8. when 表达式:Java switch 的现代版 ----------
val grade = when (age) {
in 0..6 -> "幼儿"
in 7..17 -> "少年"
in 18..40 -> "青年"
in 41..65 -> "中年"
else -> "老年"
}
println("$name 属于 $grade")
}markdown
# Java vs Kotlin —— 同样需求两种写法
## 需求 1:定义一个 User 类(含 equals / hashCode / toString)
### Java(不用 Lombok)
```java
public class User {
private final String id;
private final String name;
private final int age;
public User(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() { return id; }
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(id, user.id) &&
Objects.equals(name, user.name);
}
@Override
public int hashCode() { return Objects.hash(id, name, age); }
@Override
public String toString() {
return "User{id='" + id + "', name='" + name + "', age=" + age + '}';
}
}Kotlin
kotlin
data class User(val id: String, val name: String, val age: Int)需求 2:从用户列表里筛出成年人,按年龄倒序
Java
java
List<User> adults = users.stream()
.filter(u -> u.getAge() >= 18)
.sorted((a, b) -> Integer.compare(b.getAge(), a.getAge()))
.collect(Collectors.toList());Kotlin
kotlin
val adults = users
.filter { it.age >= 18 }
.sortedByDescending { it.age }需求 3:异步获取用户信息然后处理
Java(CompletableFuture 链)
java
fetchUser(userId)
.thenCompose(user -> fetchOrders(user.getId()))
.thenCompose(orders -> fetchPayments(orders.get(0).getId()))
.thenAccept(payments -> System.out.println(payments))
.exceptionally(ex -> {
ex.printStackTrace();
return null;
});Kotlin(协程)
kotlin
try {
val user = fetchUser(userId)
val orders = fetchOrders(user.id)
val payments = fetchPayments(orders[0].id)
println(payments)
} catch (e: Exception) {
e.printStackTrace()
}看起来像同步代码,实际是非阻塞执行,不会卡线程。