StampedLock 乐观读
版本基线:本文按 JDK 20 的
java.util.concurrent.locksAPI 编写;示例优先使用可直接编译运行的标准库能力。
学习目标
- 会使用
writeLock、readLock和tryOptimisticRead保护共享状态。 - 能在读取后调用
validate,校验失败时回退到真实读锁。 - 理解
StampedLock非可重入、基础阻塞获取的中断边界以及转换锁失败的边界。
核心知识点
专业术语
- StampedLock:用
long stamp代表一次锁定或乐观读凭证的锁工具,支持写锁、读锁和乐观读。 - stamp:锁操作返回的凭证;解锁时必须把对应 stamp 传回
unlockRead或unlockWrite。 - 乐观读(optimistic read):先不阻塞写者地读取,再用
validate(stamp)检查读取期间是否发生写入。 - validate:验证一个乐观读 stamp 在读取期间是否仍然有效;返回
false时不能使用未经保护的快照。 - 锁转换(conversion):用
tryConvertToReadLock、tryConvertToWriteLock等方法尝试改变当前 stamp 的模式。 - 非可重入(non-reentrant):同一线程再次申请同类型锁也不会因为“自己持有”而自动通过。
白话解释与边界
StampedLock 像是先快速拍一张现场照片:乐观读可以直接读取,最后用 validate 确认拍照期间没有写者改动。校验失败就丢弃这次快照,重新拿真正的读锁读取。它适合读多写少、读操作短、冲突不高且基准测试证明有收益的场景,不适合作为所有共享状态的默认锁。
stamp 不是线程身份,也不是可以随意保存和复用的令牌;它只对应一次具体的锁操作。所有真实读写锁都必须在 finally 中用正确的 stamp 解锁。StampedLock 不可重入,基础的 readLock()/writeLock() 不响应中断,但也提供 readLockInterruptibly()/writeLockInterruptibly() 和带超时的获取方法;需要更丰富的条件队列或公平策略时再选择 ReentrantReadWriteLock。
常用用法
StampedLock():创建锁
需要乐观读或锁模式转换时创建 StampedLock;先确认业务能接受 stamp 管理、非可重入和无 Condition 的边界。
// 说明:StampedLock():创建锁。
import java.util.concurrent.locks.StampedLock;
public class StampedLockCreateDemo {
public static void main(String[] args) {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;创建锁。
System.out.println("created=" + (lock != null));
// 输出:created=true
}
}StampedLock 不接受公平策略参数,也不提供 Condition。创建它之前应先确认业务能接受 stamp 管理、非重入和更复杂的异常路径。
独占写入:按戳记获得并释放写锁
修改受保护状态时使用写锁;每次成功获取返回的 stamp 都必须在 finally 中传给 unlockWrite。
// 说明:独占写入:按戳记获得并释放写锁。
import java.util.concurrent.locks.StampedLock;
public class StampedWriteLockDemo {
private final StampedLock lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:private final StampedLock lock = new StampedLock();;按戳记获得并释放写锁。
private int value;
void add(int delta) {
long stamp = lock.writeLock();
// 初始状态:stamp = lock.writeLock()。
// 作用:long stamp = lock.writeLock();;按戳记获得并释放写锁。
try {
value += delta;
} finally {
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;按戳记获得并释放写锁。
}
}
public static void main(String[] args) {
var state = new StampedWriteLockDemo();
// 初始状态:state = new StampedWriteLockDemo()。
// 作用:var state = new StampedWriteLockDemo();;按戳记获得并释放写锁。
state.add(3);
// 初始状态:state.add(3)。
// 作用:state.add(3);;按戳记获得并释放写锁。
System.out.println("write complete");
// 输出:write complete
}
}写锁排斥所有读锁和其他写锁。不要只保存一个字段级 stamp 供多个方法共用;每次成功的 writeLock() 都要由同一套生命周期在 finally 中释放。
悲观读取:按戳记获得并释放读锁
读取必须在锁保护下保持一致时使用悲观读锁;它允许读者并行,但会阻塞写者。
// 说明:悲观读取:按戳记获得并释放读锁。
import java.util.concurrent.locks.StampedLock;
public class StampedReadLockDemo {
private final StampedLock lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:private final StampedLock lock = new StampedLock();;按戳记获得并释放读锁。
private int value = 42;
// 初始状态:value = 42。
int read() {
long stamp = lock.readLock();
// 初始状态:stamp = lock.readLock()。
// 作用:long stamp = lock.readLock();;按戳记获得并释放读锁。
try {
return value;
} finally {
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;按戳记获得并释放读锁。
}
}
public static void main(String[] args) {
var state = new StampedReadLockDemo();
// 初始状态:state = new StampedReadLockDemo()。
// 作用:var state = new StampedReadLockDemo();;按戳记获得并释放读锁。
System.out.println(state.read());
// 输出:42
}
}悲观读会真正占用读锁,允许多个读者并行但会阻塞写者。无法保证读期间没有写入时,用它比错误地使用乐观读更可靠。
乐观读取:复制状态后校验戳记
读多写少且冲突较低时适合使用乐观读;复制字段后必须调用 validate,失败就丢弃快照并回退到读锁。
// 说明:乐观读取:复制状态后校验戳记。
import java.util.concurrent.locks.StampedLock;
public class OptimisticReadDemo {
private final StampedLock lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:private final StampedLock lock = new StampedLock();;复制状态后校验戳记。
private int x = 3;
// 初始状态:x = 3。
private int y = 4;
// 初始状态:y = 4。
int distanceSquared() {
long stamp = lock.tryOptimisticRead();
// 初始状态:stamp = lock.tryOptimisticRead()。
// 作用:long stamp = lock.tryOptimisticRead();;复制状态后校验戳记。
int localX = x;
// 初始状态:localX = x。
int localY = y;
// 初始状态:localY = y。
if (!lock.validate(stamp)) {
stamp = lock.readLock();
// 初始状态:stamp = lock.readLock()。
// 作用:stamp = lock.readLock();;复制状态后校验戳记。
try {
localX = x;
// 初始状态:localX = x。
localY = y;
// 初始状态:localY = y。
} finally {
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;复制状态后校验戳记。
}
}
return localX * localX + localY * localY;
}
public static void main(String[] args) {
var point = new OptimisticReadDemo();
// 初始状态:point = new OptimisticReadDemo()。
// 作用:var point = new OptimisticReadDemo();;复制状态后校验戳记。
System.out.println(point.distanceSquared());
// 输出:25
}
}乐观读期间读取到的中间值可能不一致,所以必须先把必要字段复制到局部变量,再调用 validate;校验失败时丢弃这些局部值并回退到读锁。若读取对象引用指向可变对象,校验成功也不等于对象内部已经不可变,仍需复制快照或使用不可变数据。
tryOptimisticRead:检查是否有可用的乐观凭证
只想快速探测是否能进行无阻塞读取时使用该方法;stamp 只有通过 validate 校验后才可使用。
// 说明:tryOptimisticRead:检查是否有可用的乐观凭证。
import java.util.concurrent.locks.StampedLock;
public class OptimisticStampDemo {
public static void main(String[] args) {
var lock = new StampedLock();
// 作用:var lock = new StampedLock();;检查是否有可用的乐观凭证。
// 初始状态:lock = new StampedLock()。
long stamp = lock.tryOptimisticRead();
// 初始状态:stamp = lock.tryOptimisticRead()。
// 作用:long stamp = lock.tryOptimisticRead();;检查是否有可用的乐观凭证。
System.out.println("valid=" + lock.validate(stamp));
// 输出:valid=true(校验期间没有写入时)
}
}tryOptimisticRead() 不阻塞,返回的 stamp 可能是无效值或随后失效;只有 validate 返回 true,并且读取对象本身满足快照要求时,结果才可使用。并发写入时该输出也可能为 false。
不常用但需要知道
tryReadLock(timeout):带超时的真实读锁
不能无限等待读锁时使用带超时的获取;返回零 stamp 时应及时降级或返回,而不是继续解锁无效凭证。
// 作用:通过 tryReadLock(timeout) 带超时的真实读锁。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
public class StampedReadTimeoutDemo {
public static void main(String[] args) throws InterruptedException {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;带超时的真实读锁。
long stamp = lock.tryReadLock(1, TimeUnit.MILLISECONDS);
// 初始状态:stamp = lock.tryReadLock(1, TimeUnit.MILLISECONDS)。
// 作用:long stamp = lock.tryReadLock(1, TimeUnit.MILLISECONDS);;带超时的真实读锁。
System.out.println("stampAvailable=" + (stamp != 0L));
// 输出:stampAvailable=true(没有写者占用时)
if (stamp != 0L) {
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;带超时的真实读锁。
}
}
}带超时的获取方法适合设置取消和降级边界。返回 0L 表示没有获得锁,此时不能调用 unlockRead(0L);并发竞争下输出可能是 false。
tryWriteLock(timeout):可被中断的有界写锁等待
写锁等待需要时间上限且可响应中断时使用该方法;成功后仍要在 finally 中释放对应 stamp。
// 作用:通过 tryWriteLock(timeout) 可被中断的有界写锁等待。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
public class StampedWriteTimeoutDemo {
public static void main(String[] args) throws InterruptedException {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;可被中断的有界写锁等待。
long stamp = lock.tryWriteLock(1, TimeUnit.MILLISECONDS);
// 初始状态:stamp = lock.tryWriteLock(1, TimeUnit.MILLISECONDS)。
// 作用:long stamp = lock.tryWriteLock(1, TimeUnit.MILLISECONDS);;可被中断的有界写锁等待。
System.out.println("stampAvailable=" + (stamp != 0L));
// 输出:stampAvailable=true(没有读者或写者占用时)
if (stamp != 0L) {
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;可被中断的有界写锁等待。
}
}
}tryWriteLock(timeout, unit) 的超时重载可以响应中断,是阻塞式 writeLock() 的取消替代方案。超时后要有明确的降级或失败策略,不要把 0L 当作有效 stamp。
可中断地获取锁:按访问模式选择读锁或写锁
线程需要响应取消而又必须拿真实读写锁时使用可中断版本;处理中断异常时要恢复状态或传播取消。
import java.util.concurrent.locks.StampedLock;
public class StampedInterruptibleLockDemo {
public static void main(String[] args) throws InterruptedException {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;按访问模式选择读锁或写锁。
long stamp = lock.writeLockInterruptibly();
// 初始状态:stamp = lock.writeLockInterruptibly()。
// 作用:long stamp = lock.writeLockInterruptibly();;按访问模式选择读锁或写锁。
try {
System.out.println("interruptible write acquired");
// 输出:interruptible write acquired
} finally {
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;按访问模式选择读锁或写锁。
}
}
}基础 readLock()/writeLock() 在等待时不会因为中断而抛出异常;readLockInterruptibly() 和 writeLockInterruptibly() 则会响应中断。两组方法都返回 stamp,成功后仍必须在 finally 中用对应的 unlockRead 或 unlockWrite 释放。
tryConvertToWriteLock(stamp):尝试读锁转写锁
已持有读 stamp 且希望原子升级时可以尝试转换;转换返回零表示失败,不能假设升级一定成功。
// 作用:通过 tryConvertToWriteLock(stamp) 尝试读锁转写锁。
import java.util.concurrent.locks.StampedLock;
public class StampedConvertWriteDemo {
private final StampedLock lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:private final StampedLock lock = new StampedLock();;尝试读锁转写锁。
private int value;
void increment() {
long stamp = lock.readLock();
// 初始状态:stamp = lock.readLock()。
// 作用:long stamp = lock.readLock();;尝试读锁转写锁。
try {
long converted = lock.tryConvertToWriteLock(stamp);
// 初始状态:converted = lock.tryConvertToWriteLock(stamp)。
// 作用:long converted = lock.tryConvertToWriteLock(stamp);;尝试读锁转写锁。
if (converted != 0L) {
stamp = converted;
// 初始状态:stamp = converted。
value++;
System.out.println("converted=true");
// 输出:converted=true
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;尝试读锁转写锁。
stamp = 0L;
// 初始状态:stamp = 0L。
} else {
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;尝试读锁转写锁。
stamp = 0L;
// 初始状态:stamp = 0L。
long writeStamp = lock.writeLock();
// 初始状态:writeStamp = lock.writeLock()。
// 作用:long writeStamp = lock.writeLock();;尝试读锁转写锁。
try {
value++;
System.out.println("converted=false, write acquired");
// 输出:converted=false, write acquired
} finally {
lock.unlockWrite(writeStamp);
// 初始状态:lock.unlockWrite(writeStamp)。
// 作用:lock.unlockWrite(writeStamp);;尝试读锁转写锁。
}
}
} finally {
if (stamp != 0L) {
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;尝试读锁转写锁。
}
}
}
public static void main(String[] args) {
new StampedConvertWriteDemo().increment();
// 初始状态:new StampedConvertWriteDemo().increment()。
// 作用:new StampedConvertWriteDemo().increment();;尝试读锁转写锁。
}
}转换只是一次尝试,不保证成功;失败后必须释放旧的读锁,再申请写锁,并重新检查业务条件。示例在转换成功时用 unlockWrite,在失败路径中先 unlockRead,不能把同一个 stamp 无条件交给某一种解锁方法。
tryConvertToReadLock(stamp):写锁降级为读锁
写入完成后还需保持读保护时使用转换降级;转换成功后要用新的读 stamp 解锁,失败则继续使用旧模式。
// 作用:通过 tryConvertToReadLock(stamp) 写锁降级为读锁。
import java.util.concurrent.locks.StampedLock;
public class StampedConvertReadDemo {
public static void main(String[] args) {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;写锁降级为读锁。
long stamp = lock.writeLock();
// 初始状态:stamp = lock.writeLock()。
// 作用:long stamp = lock.writeLock();;写锁降级为读锁。
try {
long readStamp = lock.tryConvertToReadLock(stamp);
// 初始状态:readStamp = lock.tryConvertToReadLock(stamp)。
// 作用:long readStamp = lock.tryConvertToReadLock(stamp);;写锁降级为读锁。
if (readStamp != 0L) {
stamp = readStamp;
// 初始状态:stamp = readStamp。
System.out.println("downgraded=true");
// 输出:downgraded=true
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;写锁降级为读锁。
stamp = 0L;
// 初始状态:stamp = 0L。
} else {
System.out.println("downgraded=false");
// 输出:downgraded=false
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;写锁降级为读锁。
stamp = 0L;
// 初始状态:stamp = 0L。
}
} finally {
if (stamp != 0L) {
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;写锁降级为读锁。
}
}
}
}从写锁转换为读锁通常比释放再重新获取更连续,但转换仍可能失败。成功后 stamp 已变成读锁凭证,必须使用 unlockRead;失败时原写锁仍由当前线程持有。
非可重入:同一线程不能重复获得写锁
同一线程需要多层调用时不要把 StampedLock 当可重入锁;重复获取可能阻塞,应改用显式协议或 ReentrantLock。
import java.util.concurrent.locks.StampedLock;
public class StampedNonReentrantDemo {
public static void main(String[] args) {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;同一线程不能重复获得写锁。
long stamp = lock.writeLock();
// 初始状态:stamp = lock.writeLock()。
// 作用:long stamp = lock.writeLock();;同一线程不能重复获得写锁。
try {
long nested = lock.tryWriteLock();
// 初始状态:nested = lock.tryWriteLock()。
// 作用:long nested = lock.tryWriteLock();;同一线程不能重复获得写锁。
System.out.println("reentered=" + (nested != 0L));
// 输出:reentered=false
if (nested != 0L) {
lock.unlockWrite(nested);
// 初始状态:lock.unlockWrite(nested)。
// 作用:lock.unlockWrite(nested);;同一线程不能重复获得写锁。
}
} finally {
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;同一线程不能重复获得写锁。
}
}
}tryWriteLock() 在当前线程已经持有写锁时不会像 ReentrantLock 一样自动成功;如果把它换成阻塞式 writeLock(),同一线程可能永久等待。嵌套调用前必须重新设计锁边界,或改用可重入锁。
tryWriteLock(timeout):用超时获取响应中断
需要同时限制等待时间并观察中断时使用超时获取;失败和中断都必须明确返回、重试或取消路径。
// 作用:通过 tryWriteLock(timeout) 用超时获取响应中断。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
public class StampedInterruptBoundaryDemo {
public static void main(String[] args) {
var lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:var lock = new StampedLock();;用超时获取响应中断。
Thread.currentThread().interrupt();
// 初始状态:Thread.currentThread().interrupt()。
// 作用:Thread.currentThread().interrupt();;用超时获取响应中断。
try {
lock.tryWriteLock(1, TimeUnit.MILLISECONDS);
// 初始状态:lock.tryWriteLock(1, TimeUnit.MILLISECONDS)。
// 作用:lock.tryWriteLock(1, TimeUnit.MILLISECONDS);;用超时获取响应中断。
System.out.println("not interrupted");
// 输出:不会执行到这里
} catch (InterruptedException ex) {
System.out.println("interrupted");
// 输出:interrupted
} finally {
Thread.interrupted();
// 初始状态:Thread.interrupted()。
// 作用:Thread.interrupted();;用超时获取响应中断。
}
}
}基础阻塞式 readLock() 和 writeLock() 不响应中断;readLockInterruptibly()、writeLockInterruptibly() 以及带时间参数的获取方法可以在等待期间响应中断。若业务经常依赖多个条件队列或公平策略,ReentrantReadWriteLock 往往更合适。
简单案例
坐标快照:乐观读失败后回退
import java.util.concurrent.locks.StampedLock;
public class PointSnapshotDemo {
private final StampedLock lock = new StampedLock();
// 初始状态:lock = new StampedLock()。
// 作用:private final StampedLock lock = new StampedLock();;乐观读失败后回退。
private int x;
private int y;
void move(int deltaX, int deltaY) {
long stamp = lock.writeLock();
// 初始状态:stamp = lock.writeLock()。
// 作用:long stamp = lock.writeLock();;乐观读失败后回退。
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
// 初始状态:lock.unlockWrite(stamp)。
// 作用:lock.unlockWrite(stamp);;乐观读失败后回退。
}
}
String snapshot() {
long stamp = lock.tryOptimisticRead();
// 初始状态:stamp = lock.tryOptimisticRead()。
// 作用:long stamp = lock.tryOptimisticRead();;乐观读失败后回退。
int localX = x;
// 初始状态:localX = x。
int localY = y;
// 初始状态:localY = y。
if (!lock.validate(stamp)) {
stamp = lock.readLock();
// 初始状态:stamp = lock.readLock()。
// 作用:stamp = lock.readLock();;乐观读失败后回退。
try {
localX = x;
// 初始状态:localX = x。
localY = y;
// 初始状态:localY = y。
} finally {
lock.unlockRead(stamp);
// 初始状态:lock.unlockRead(stamp)。
// 作用:lock.unlockRead(stamp);;乐观读失败后回退。
}
}
return "(" + localX + "," + localY + ")";
}
public static void main(String[] args) {
var point = new PointSnapshotDemo();
// 初始状态:point = new PointSnapshotDemo()。
// 作用:var point = new PointSnapshotDemo();;乐观读失败后回退。
point.move(2, 3);
// 初始状态:point.move(2, 3)。
// 作用:point.move(2, 3);;乐观读失败后回退。
System.out.println(point.snapshot());
// 输出:(2,3)
}
}示例读取的是两个基本类型并在校验后一起返回,适合说明乐观读的快照边界。真实业务中如果快照包含集合、数组或可变对象,应在写锁下复制为不可变结果,不能只校验一个引用。
易混点
tryOptimisticRead()不代表已经获得读锁;必须读取局部快照并调用validate,失败就回退。- stamp 不是锁对象也不是线程所有权;真实读锁用
unlockRead,写锁用unlockWrite,不能混用。 StampedLock不可重入;同一线程重复申请写锁不会像ReentrantLock一样自动成功。readLock()和writeLock()的基础阻塞获取不响应中断;需要取消时可用readLockInterruptibly()、writeLockInterruptibly()或带超时的获取方法。- 转换方法返回
0L表示失败,失败后旧锁仍可能由当前线程持有,必须按对应模式解锁再重新获取。 StampedLock没有Condition,也不保证公平;不要为了“乐观读”而牺牲可维护性。
课后小问
为什么乐观读必须在读取后调用
validate? 答案:读取期间可能有写者修改共享状态,只有校验成功才能说明这次局部快照没有被写入打断。 解析:即使两个字段都读到了,写入也可能发生在两个读取动作之间;校验失败时必须丢弃局部值并用真实读锁重读。StampedLock为什么不能直接当作ReentrantLock使用? 答案:它不是可重入的Lock实现,基础阻塞获取不响应中断,也没有Condition;它还要求调用者管理 stamp。 解析:它虽然提供readLockInterruptibly()、writeLockInterruptibly()和超时重载,但这些能力仍比ReentrantLock更分散。若业务需要可重入、条件队列或统一的中断等待,应选择其他锁。tryConvertToWriteLock返回0L后应该怎么做? 答案:按当前 stamp 解锁旧模式,重新申请写锁,并重新检查需要更新的条件。 解析:转换是一次无阻塞尝试,不成功并不表示业务条件已经失效,也不能把0L传给unlockWrite。
本节小结
StampedLock通过 stamp 区分写锁、悲观读锁和乐观读凭证。- 乐观读必须复制局部快照并用
validate校验,失败后回退到真实读锁。 - 转换锁是尝试而非保证;每条路径都要按实际模式在
finally中解锁。 - 非可重入、基础获取不响应中断和无 Condition 是重要边界,读多写少的收益必须用基准证明。
快速回顾
- 会写
writeLock/readLock与对应unlock(stamp)的模板。 - 能用
tryOptimisticRead、局部快照和validate完成安全乐观读。 - 能解释转换失败、stamp 为零、非可重入和不可中断的含义。
- 知道什么时候应放弃 StampedLock,改用更直观的可重入读写锁或不可变快照。
阿源的知识库