1. 写锁
-
独占性:
写锁是完全独占的。在一个线程获取了写锁后,其他线程既不能获得写锁,也不能获得读锁或乐观读锁。 -
使用方法:
通过writeLock()
方法获取写锁,返回一个 stamp,释放时需要传入这个 stamp。StampedLock stampedLock = new StampedLock(); long stamp = stampedLock.writeLock(); try { // 对共享资源进行写操作 } finally { stampedLock.unlockWrite(stamp); }
2. 读锁
-
共享性:
读锁允许多个线程同时获取,只要没有线程持有写锁。 -
使用方法:
使用readLock()
方法获得读锁,同样返回一个 stamp,用于后续的释放操作。long stamp = stampedLock.readLock(); try { // 执行读操作 } finally { stampedLock.unlockRead(stamp); }
3. 乐观读锁
-
非阻塞特性:
乐观读锁不是一种真正意义上的锁,它不会阻塞写操作。调用tryOptimisticRead()
方法时,会返回一个 stamp,这个 stamp 表示一个“乐观”状态。 -
使用方法和验证:
在进行乐观读时,先获取 stamp,然后执行读操作,最后使用validate(stamp)
方法验证在读期间数据是否发生了修改。如果验证失败,说明在读期间有写操作发生,通常退化为读锁重新读取数据。long stamp = stampedLock.tryOptimisticRead(); // 进行读操作 // ... if (!stampedLock.validate(stamp)) { // 如果验证失败,说明期间有写操作,退化为获取读锁重试 stamp = stampedLock.readLock(); try { // 重新进行读操作 } finally { stampedLock.unlockRead(stamp); } }
4. 限制
-
不支持条件变量:
与ReentrantLock
不同,StampedLock
不支持像Condition
这样的条件变量。无法使用await()
或signal()
来等待特定条件的发生。 -
不支持锁重入:
StampedLock
是非重入的。如果同一个线程已经持有写锁,再次尝试获取写锁或读锁时,会导致阻塞或错误。- 写锁→读锁:当线程持有写锁时,可以安全地转换为读操作(称为锁降级),但这必须遵循严格的操作顺序。
- 读锁→写锁:从读锁直接升级为写锁是不支持的,会导致死锁风险,因此需要先释放读锁,再获取写锁。
Stamped:
-
标识锁的状态
每次获取锁(无论是读锁、写锁还是乐观读锁)时,都会返回一个 stamp,这个 stamp 内部编码了当前锁的状态(包括版本信息)。当写操作发生时,版本会递增,从而使之前获取的 stamp 失效。 -
用于解锁操作
获取锁后,必须在释放锁时传入相同的 stamp。通过这种方式,StampedLock
能够验证释放操作是否对应正确的锁状态,保证解锁操作的正确性。long stamp = stampedLock.writeLock(); try { // 执行写操作 } finally { stampedLock.unlockWrite(stamp); }
-
乐观读锁的验证
使用tryOptimisticRead()
获得一个乐观读锁时,会返回一个 stamp。此时,线程可以不阻塞地读取数据,但在读取完成后,调用validate(stamp)
方法来检查在此期间是否有写操作发生。如果返回false
,表示期间有修改,读取的数据可能不一致,通常退化为读锁重新读取数据。long stamp = stampedLock.tryOptimisticRead(); // 执行读操作 if (!stampedLock.validate(stamp)) { // 如果验证失败,则获取读锁重新读取数据 stamp = stampedLock.readLock(); try { // 再次执行读操作 } finally { stampedLock.unlockRead(stamp); } }