Compose的快照系统基于 MVCC (多版本并发控制) 思想,类似于数据库事务。
ini
StateObject (mutableStateOf(0))
│
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Record id=7 │───►│ Record id=5 │───►│ Record id=1 │
│ value=100 │ │ value=50 │ │ value=0 │
└──────────────┘ └──────────────┘ └──────────────┘
(最新) (初始)
目标是保证:
- 隔离性: 不同快照的修改相互隔离
- 原子性: 快照应用是原子操作
- 可观察性: 支持状态读写追踪
示例代码
kotlin
class Person {
var name: MutableState<String> = mutableStateOf("")
}
val person = Person()
person.name.value = "令狐冲"
val snapshot = Snapshot.takeSnapshot()
person.name.value = "任盈盈"
println("第一次打印:${person.name.value}") // 输出: 任盈盈
snapshot.enter {
println("第二次打印:${person.name.value}") // 输出: 令狐冲
}
println("第三次打印:${person.name.value}") // 输出: 任盈盈
第二次打印输出令狐冲,说明快照保存之前的状态,快照是如何保存之前的状态?第三次打印任盈盈,说明enter方法执行完成后,又恢复成最新状态了,这又是为何?下面将一步一步的解析示例中的每一行代码。
第一步:创建 MutableState (mutableStateOf(""))
当执行 mutableStateOf("") 时:
源码位置:SnapshotState.kt
kotlin
public fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy(),
): MutableState<T> = createSnapshotMutableState(value, policy)
源码位置:SnapshotState.kt
kotlin
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>,
) : StateObjectImpl(), SnapshotMutableState<T> {
private var next: StateStateRecord<T> =
currentSnapshot().let { snapshot ->
StateStateRecord(snapshot.snapshotId, value).also {
if (snapshot !is GlobalSnapshot) {
it.next = StateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
}
}
}
// ...
}
发生了什么?
- 获取当前快照 :
currentSnapshot()返回 GlobalSnapshot(假设 id=1) - 创建 StateRecord : 创建
StateStateRecord(snapshotId=1, value="") - 内存结构:
ini
person.name (SnapshotMutableStateImpl)
│
└── next ──► StateStateRecord
├── snapshotId = 1
├── value = ""
└── next = null
第二步:第一次赋值 (person.name.value = "令狐冲")
源码位置:SnapshotState.kt
kotlin
override var value: T
get() = next.readable(this).value
set(value) =
next.withCurrent {
if (!policy.equivalent(it.value, value)) {
next.overwritable(this, it) { this.value = value }
}
}
当 value = "令狐冲" 时:
- 调用
withCurrent: 找到当前可读的记录 - 检查等价性 :
policy.equivalent("", "令狐冲")返回false - 调用
overwritable
源码位置:Snapshot.kt
kotlin
internal inline fun <T : StateRecord, R> T.overwritable(
state: StateObject,
candidate: T,
block: T.() -> R,
): R {
val snapshot: Snapshot
return sync {
snapshot = Snapshot.current // GlobalSnapshot (id=1)
this.overwritableRecord(state, snapshot, candidate).block()
}
.also { notifyWrite(snapshot, state) }
}
源码位置:Snapshot.kt
kotlin
internal fun <T : StateRecord> T.overwritableRecord(
state: StateObject,
snapshot: Snapshot,
candidate: T,
): T {
val id = snapshot.snapshotId // id=1
// 如果 candidate 已经是当前快照创建的,直接返回
if (candidate.snapshotId == id) return candidate
// 否则创建新记录
val newData = sync { newOverwritableRecordLocked(state) }
newData.snapshotId = id
// ...
return newData
}
关键点
因为 candidate.snapshotId == 1 等于 snapshot.snapshotId == 1,直接修改现有记录!
执行后的内存结构
ini
person.name (SnapshotMutableStateImpl)
│
└── next ──► StateStateRecord
├── snapshotId = 1
├── value = "令狐冲" ← 被覆盖
└── next = null
第三步:创建快照 (Snapshot.takeSnapshot())
源码位置:Snapshot.kt
kotlin
public fun takeSnapshot(readObserver: ((Any) -> Unit)? = null): Snapshot =
currentSnapshot().takeNestedSnapshot(readObserver)
调用 GlobalSnapshot.takeNestedSnapshot():
源码位置:Snapshot.kt
kotlin
override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot =
creatingSnapshot(...) { actualReadObserver, _ ->
takeNewSnapshot { invalid ->
ReadonlySnapshot(
snapshotId = sync { nextSnapshotId.also { nextSnapshotId += 1 } }, // id=2
invalid = invalid,
readObserver = actualReadObserver,
)
}
}
源码位置:Snapshot.kt
kotlin
private fun <T : Snapshot> takeNewSnapshot(block: (invalid: SnapshotIdSet) -> T): T =
advanceGlobalSnapshot { invalid ->
val result = block(invalid)
sync { openSnapshots = openSnapshots.set(result.snapshotId) }
result
}
源码位置:Snapshot.kt
kotlin
private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
val globalSnapshot = globalSnapshot
val result = sync {
// 重置全局快照,分配新 ID
resetGlobalSnapshotLocked(globalSnapshot, block)
}
// ...
return result
}
源码位置:Snapshot.kt
kotlin
private fun <T> resetGlobalSnapshotLocked(
globalSnapshot: GlobalSnapshot,
block: (invalid: SnapshotIdSet) -> T,
): T {
val snapshotId = globalSnapshot.snapshotId // 当前是 1
val result = block(openSnapshots.clear(snapshotId)) // invalid = {空}
val nextGlobalSnapshotId = nextSnapshotId // 获取下一个 ID (2)
nextSnapshotId += 1
openSnapshots = openSnapshots.clear(snapshotId)
globalSnapshot.snapshotId = nextGlobalSnapshotId // GlobalSnapshot 变成 id=3
globalSnapshot.invalid = openSnapshots // {2} - 因为快照 2 还未关闭
openSnapshots = openSnapshots.set(nextGlobalSnapshotId) // {2, 3}
return result
}
执行顺序详解
- 进入时: GlobalSnapshot.id=1, nextSnapshotId=2, openSnapshots={1}
- 创建 ReadonlySnapshot: id=2, invalid={}(空集合!)
- 更新 GlobalSnapshot: id=3
- 退出时: GlobalSnapshot.id=3, nextSnapshotId=4, openSnapshots={2,3}
关键点:snapshot 的 invalid 集合
ini
ReadonlySnapshot:
snapshotId = 2
invalid = {} ← 空集合!这意味着它能看到 id <= 2 的所有记录
执行后的状态
ini
GlobalSnapshot: id=3, invalid={2}
ReadonlySnapshot (我们拿到的): id=2, invalid={}
person.name 的记录链:
StateStateRecord(snapshotId=1, value="令狐冲") → null
第四步:第二次赋值 (person.name.value = "任盈盈")
再次执行 overwritable:
源码位置:Snapshot.kt
kotlin
internal fun <T : StateRecord> T.overwritableRecord(
state: StateObject,
snapshot: Snapshot,
candidate: T,
): T {
val id = snapshot.snapshotId // 现在是 3(GlobalSnapshot)
// candidate.snapshotId = 1, id = 3
// 1 != 3,需要创建新记录!
if (candidate.snapshotId == id) return candidate
val newData = sync { newOverwritableRecordLocked(state) }
newData.snapshotId = id // 设为 3
// ...
return newData
}
源码位置:Snapshot.kt
kotlin
internal fun <T : StateRecord> T.newOverwritableRecordLocked(state: StateObject): T {
return (usedLocked(state) as T?)?.apply { snapshotId = SnapshotIdMax }
?: create(SnapshotIdMax).apply {
this.next = state.firstStateRecord // 指向旧记录
state.prependStateRecord(this as T) // 插入链表头部
} as T
}
执行后的内存结构
ini
person.name (SnapshotMutableStateImpl)
│
└── next ──► StateStateRecord (新)
├── snapshotId = 3
├── value = "任盈盈"
└── next ──► StateStateRecord (旧)
├── snapshotId = 1
├── value = "令狐冲"
└── next = null
这就是关键:旧记录被保留了!
第五步:第一次打印 (在全局快照中读取)
kotlin
println("第一次打印:${person.name.value}")
源码位置:SnapshotState
kotlin
override var value: T
get() = next.readable(this).value
源码位置:Snapshot.kt
kotlin
public fun <T : StateRecord> T.readable(state: StateObject): T {
val snapshot = Snapshot.current // GlobalSnapshot: id=3, invalid={2}
snapshot.readObserver?.invoke(state)
return readable(this, snapshot.snapshotId, snapshot.invalid)
?: // fallback...
}
源码位置:Snapshot.kt
kotlin
private fun <T : StateRecord> readable(r: T, id: SnapshotId, invalid: SnapshotIdSet): T? {
// id=3, invalid={2}
var current: StateRecord? = r
var candidate: StateRecord? = null
while (current != null) {
if (valid(current, id, invalid)) {
candidate =
if (candidate == null) current
else if (candidate.snapshotId < current.snapshotId) current else candidate
}
current = current.next
}
return candidate as? T
}
源码位置:Snapshot.kt
kotlin
private fun valid(
currentSnapshot: SnapshotId,
candidateSnapshot: SnapshotId,
invalid: SnapshotIdSet,
): Boolean {
return candidateSnapshot != INVALID_SNAPSHOT &&
candidateSnapshot <= currentSnapshot &&
!invalid.get(candidateSnapshot)
}
版本选择过程
GlobalSnapshot: id=3, invalid={2}
| 记录 | snapshotId | valid() 检查 | 结果 |
|---|---|---|---|
| 记录1 | 3 | 3 != 0 ✓, 3 <= 3 ✓, 3 不在 {2} ✓ | 有效 |
| 记录2 | 1 | 1 != 0 ✓, 1 <= 3 ✓, 1 不在 {2} ✓ | 有效 |
选择 snapshotId 最大的有效记录 → 记录1 (snapshotId=3, value="任盈盈")
输出: 任盈盈
第六步:进入快照 (snapshot.enter { ... })
源码位置:Snapshot.kt
kotlin
public inline fun <T> enter(block: () -> T): T {
val previous = makeCurrent()
try {
return block()
} finally {
restoreCurrent(previous)
}
}
源码位置:Snapshot.kt
kotlin
internal open fun makeCurrent(): Snapshot? {
val previous = threadSnapshot.get()
threadSnapshot.set(this) // 将 ReadonlySnapshot 设为当前快照
return previous
}
现在 Snapshot.current 返回我们的 ReadonlySnapshot (id=2, invalid={})
第七步:第二次打印 (在 ReadonlySnapshot 中读取)
kotlin
println("第二次打印:${person.name.value}")
再次调用 readable():
ReadonlySnapshot: id=2, invalid={}
版本选择过程
| 记录 | snapshotId | valid() 检查 | 结果 |
|---|---|---|---|
| 记录1 | 3 | 3 != 0 ✓, 3 <= 2 ✗ | 无效 |
| 记录2 | 1 | 1 != 0 ✓, 1 <= 2 ✓, 1 不在 {} ✓ | 有效 |
选择唯一有效的记录 → 记录2 (snapshotId=1, value="令狐冲")
输出: 令狐冲
为什么记录1无效?
kotlin
candidateSnapshot <= currentSnapshot
3 <= 2 // FALSE!
记录1的 snapshotId=3 大于 ReadonlySnapshot 的 id=2,所以它对这个快照是"未来"的,不可见。
第八步:退出快照并第三次打印
kotlin
// snapshot.enter 返回后,自动调用 restoreCurrent(previous)
// Snapshot.current 又变回 GlobalSnapshot (id=3)
println("第三次打印:${person.name.value}")
与第五步相同的逻辑,输出: 任盈盈
核心原理总结
1. MVCC 多版本
每次在新快照 中修改状态,都会创建新记录而不是覆盖旧记录:
bash
链表头 → Record(id=3, "任盈盈") → Record(id=1, "令狐冲") → null
2. 版本选择算法
readable() 函数通过三个条件选择版本:
kotlin
candidateSnapshot != INVALID_SNAPSHOT && // 不是无效 ID
candidateSnapshot <= currentSnapshot && // 不超过当前快照 ID
!invalid.get(candidateSnapshot) // 不在 invalid 集合中
3. invalid 集合的作用
- 快照创建时,记录哪些快照 ID 是"不可见"的
- 对于 ReadonlySnapshot,创建时的 invalid 是空的
- 对于 GlobalSnapshot,invalid 包含所有未 apply 的 MutableSnapshot
4. ThreadLocal 实现线程隔离
kotlin
private val threadSnapshot = SnapshotThreadLocal<Snapshot>()
internal fun currentSnapshot(): Snapshot =
threadSnapshot.get() ?: globalSnapshot
图解完整流程
ini
时间线:
───────────────────────────────────────────────────────►
T1: mutableStateOf("")
GlobalSnapshot id=1
Record: [id=1, value=""]
T2: value = "令狐冲"
GlobalSnapshot id=1 (未变)
Record: [id=1, value="令狐冲"] ← 直接覆盖,因为同一快照
T3: takeSnapshot()
GlobalSnapshot id=3 (推进了!)
ReadonlySnapshot id=2, invalid={}
Record: 不变
T4: value = "任盈盈"
GlobalSnapshot id=3
创建新记录!
Record链: [id=3, value="任盈盈"] → [id=1, value="令狐冲"]
T5: 全局读取 (GlobalSnapshot id=3, invalid={2})
选择: id=3 的记录 (3 <= 3 且 3 不在 {2})
结果: "任盈盈"
T6: snapshot.enter 读取 (ReadonlySnapshot id=2, invalid={})
检查 id=3: 3 <= 2? NO → 无效
检查 id=1: 1 <= 2? YES → 有效
结果: "令狐冲"
T7: 全局读取
同 T5
结果: "任盈盈"
这就是快照能看到"过去"状态的完整原理!