ReadWriteLock 支持两种模式:一种是读锁,一种是写锁。而 StampedLock 支持三种模式,分别是:写锁、悲观读锁和乐观读。

**其中,写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似,允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的。不同的是:StampedLock 里的写锁和悲观读锁加锁成功之后,都会返回一个 stamp;然后解锁的时候,需要传入这个 stamp。

  1. final StampedLock sl = new StampedLock();
  2. // 获取 / 释放悲观读锁示意代码
  3. long stamp = sl.readLock();
  4. try {
  5. // 省略业务相关代码
  6. } finally {
  7. sl.unlockRead(stamp);
  8. }
  9. // 获取 / 释放写锁示意代码
  10. long stamp = sl.writeLock();
  11. try {
  12. // 省略业务相关代码
  13. } finally {
  14. sl.unlockWrite(stamp);
  15. }

image.png

StampedLock 的性能之所以比 ReadWriteLock 还要好,其关键是 StampedLock 支持乐观读的方式。ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。
注意这里,我们用的是“乐观读”这个词,而不是“乐观读锁”,是要提醒你,乐观读这个操作是无锁的,所以相比较 ReadWriteLock 的读锁,乐观读的性能更好一些。

  1. class Point {
  2. private int x, y;
  3. final StampedLock sl = new StampedLock();
  4. // 计算到原点的距离
  5. int distanceFromOrigin() {
  6. // 乐观读
  7. long stamp = sl.tryOptimisticRead();
  8. // 读入局部变量,
  9. // 读的过程数据可能被修改
  10. int curX = x, curY = y;
  11. // 判断执行读操作期间,
  12. // 是否存在写操作,如果存在,
  13. // 则 sl.validate 返回 false
  14. if (!sl.validate(stamp)){
  15. // 升级为悲观读锁
  16. stamp = sl.readLock();
  17. try {
  18. curX = x;
  19. curY = y;
  20. } finally {
  21. // 释放悲观读锁
  22. sl.unlockRead(stamp);
  23. }
  24. }
  25. return Math.sqrt(
  26. curX * curX + curY * curY);
  27. }
  28. }

总结

StampedLock 的使用看上去有点复杂,但是如果你能理解乐观锁背后的原理,使用起来还是比较流畅的。建议你认真揣摩 Java 的官方示例,这个示例基本上就是一个最佳实践。我们把 Java 官方示例精简后,形成下面的代码模板,建议你在实际工作中尽量按照这个模板来使用 StampedLock

StampedLock 读模板:

  1. final StampedLock sl = new StampedLock();
  2. // 乐观读
  3. long stamp = sl.tryOptimisticRead();
  4. // 读入方法局部变量
  5. ......
  6. // 校验 stamp
  7. if (!sl.validate(stamp)){//相当于mysql中乐观锁中的version
  8. // 升级为悲观读锁
  9. stamp = sl.readLock();
  10. try {
  11. // 读入方法局部变量
  12. .....
  13. } finally {
  14. // 释放悲观读锁
  15. sl.unlockRead(stamp);
  16. }
  17. }
  18. // 使用方法局部变量执行业务操作
  19. ......

StampedLock 写模板:

  1. long stamp = sl.writeLock();
  2. try {
  3. // 写共享变量
  4. ......
  5. } finally {
  6. sl.unlockWrite(stamp);
  7. }