首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >架构师四面遭遇StampedLock,这么应对保拿offer

架构师四面遭遇StampedLock,这么应对保拿offer

作者头像
JavaEdge
修改2025-12-21 19:17:59
修改2025-12-21 19:17:59
3390
举报
文章被收录于专栏:JavaEdgeJavaEdge

本文已收录在Github关注我,紧跟本系列专栏文章,咱们下篇再续!

  • 🚀 魔都架构师 | 全网30W技术追随者
  • 🔧 大厂分布式系统/数据中台实战专家
  • 🏆 主导交易系统百万级流量调优 & 车联网平台架构
  • 🧠 AIGC应用开发先行者 | 区块链落地实践者
  • 🌍 以技术驱动创新,我们的征途是改变世界!
  • 👉 实战干货:编程严选网

0 前言

读写锁允许多线程同时读共享变量,适用读多写少。读多写少场景还能更快吗?有的,JDK8提供StampedLock,性能比读写锁还好。

1 锁模式

ReadWriteLock支持读、写锁两种锁模式。而StampedLock支持三种:

  • 写锁
  • 悲观读锁
  • 乐观读

写锁、悲观读锁和ReadWriteLock的写锁、读锁的语义类似。不同在于:StampedLock写锁、悲观读锁加锁成功后,会返回一个stamp;释放锁时,需传入该stamp。

代码语言:java
复制
private final static StampedLock STAMPED_LOCK = new StampedLock();

public static void main(String[] args) throws Exception {...}

private static void add() {
    // see!!!
    long stamp = STAMPED_LOCK.writeLock();
    try {
        count++;
    } finally {
        // see!!!
        STAMPED_LOCK.unlock(stamp);
    }
}

2 StampedLock性能为何优于ReadWriteLock

在于支持乐观读:

  • ReadWriteLock支持多线程同时读,但当多线程读时,所有写操作被阻塞
  • 而StampedLock提供乐观读,允许一个线程获取写锁,即不是所有写操作都被阻塞

乐观读操作无锁,所以相比ReadWriteLock读锁,乐观读性能更好。

3 实践案例

计算到原点的距离

代码语言:java
复制
class Point {
    private int x, y;
    final StampedLock stampedLock = new StampedLock();

    public int distanceFromOrigin() {
        // 1. 乐观读, 无锁
        long stamp = stampedLock.tryOptimisticRead();

        // 2. 乐观读无锁,所以共享变量x、y读入局部变量时,x、y可能被其他线程修改
        int curX = x, curY = y;

        /*
         * 3. 所以读完后,还要再验证是否存在写操作
         * 判断执行读操作期间,是否存在写操作
         * 若存在,则stampedLock.validate返false
         */
        if (!stampedLock.validate(stamp)) {
            // 升级为悲观读锁
            stamp = stampedLock.readLock();
            try {
                curX = x;
                curY = y;
            } finally {
                // 释放悲观读锁
                stampedLock.unlockRead(stamp);
            }
        }

        return (int) Math.sqrt(curX * curX + curY * curY);
    }
}

若执行乐观读过程中,存在写操作,会把乐观读升级为悲观读锁。

这很好,否则就要在一个循环里反复执行乐观读,直到执行乐观读操作的期间没有写操作(这样才能保证x和y的正确性和一致性),而循环读浪费大量CPU。

升级为悲观读锁,代码简练不易错。

4 理解乐观读

类比StampedLock乐观读和DB乐观锁。

DB乐观锁使用场景

一个模块,会有多个人通过前端同时修改同一条订单,咋保证订单数据的线程安全?

用乐观锁。订单表加个数值型版本号字段version,每次更新记录时,version+1。生产订单的UI在展示的时候,需查询DB,此时将该version字段和其他业务字段一起返回给生产订单UI。

若用户查询的生产订单的id=777:

代码语言:sql
复制
select id,... ,version
from product_doc
where id=777

用户在前端执行保存操作时,后台用如下SQL更新生产订单,假设该条生产订单version=9。

代码语言:sql
复制
update product_doc 
set version=version+1,...
where id=777 and version=9

若该SQL语句执行成功并且返回1,说明前端执行查询操作到执行保存操作期间,没有其他人修改过这条数据。因为如果这期间其他人修改过这条数据,那么版本号字段一定会大于9。

数据库里的乐观锁,查询时需将 version 字段查出来,更新时要用version字段做验证。这个 version 字段就类似于StampedLock里面的stamp。

5 StampedLock踩坑记

读多写少场景,StampedLock性能很好,可替代ReadWriteLock,但StampedLock:

  • 不可重入
  • 悲观读锁、写锁都不支持条件变量

若线程阻塞在StampedLock的readLock()或writeLock(),此时调用该阻塞线程的interrupt(),会导致CPU飙升。

案例

线程T1获取写锁后将自己阻塞,线程T2尝试获取悲观读锁,也会阻塞;若此时调用线程T2的interrupt()来中断线程T2,会发现线程T2所在CPU飙升100%。

代码语言:java
复制
final StampedLock lock = new StampedLock();

Thread T1 = new Thread(()->{
  // 获取写锁
  lock.writeLock();
  // 永远阻塞在此处,不释放写锁
  LockSupport.park();
});
T1.start();

// 保证T1获取写锁
Thread.sleep(100);
Thread T2 = new Thread(()->
  // 阻塞在悲观读锁
  lock.readLock()
);
T2.start();
// 保证T2阻塞在读锁
Thread.sleep(100);
// 中断线程T2
// 会导致线程T2所在CPU飙升
T2.interrupt();
T2.join();

所以用StampedLock一定别调用中断。若需支持中断功能,用可中断的悲观读锁readLockInterruptibly()和写锁writeLockInterruptibly()。

6 总结

StampedLock使用看着复杂,但若理解乐观锁原理,用起来还是流畅。

认真揣摩示例,它就是个最佳实践。示例形成的代码模板,工作尽量直接套用:

StampedLock读模板

代码语言:java
复制
final StampedLock sl = new StampedLock();

// 乐观读
long stamp = 
  sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验stamp
if (!sl.validate(stamp)){
  // 升级为悲观读锁
  stamp = sl.readLock();
  try {
    // 读入方法局部变量
    .....
  } finally {
    //释放悲观读锁
    sl.unlockRead(stamp);
  }
}
//使用方法局部变量执行业务操作

StampedLock写模板

代码语言:java
复制
long stamp = sl.writeLock();
try {
  // 写共享变量
  ......
} finally {
  sl.unlockWrite(stamp);
}

StampedLock支持锁的降级(通过tryConvertToReadLock()方法实现)和升级(通过tryConvertToWriteLock()方法实现)。

代码语言:java
复制
private double x, y;
final StampedLock sl = new StampedLock();
// 存在问题的方法
void moveIfAtOrigin(double newX, double newY){
 long stamp = sl.readLock();
 try {
  while(x == 0.0 && y == 0.0){
    long ws = sl.tryConvertToWriteLock(stamp);
    if (ws != 0L) {
      x = newX;
      y = newY;
      break;
    } else {
      sl.unlockRead(stamp);
      stamp = sl.writeLock();
    }
  }
 } finally {
  sl.unlock(stamp);
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 前言
  • 1 锁模式
  • 2 StampedLock性能为何优于ReadWriteLock
  • 3 实践案例
  • 4 理解乐观读
    • DB乐观锁使用场景
  • 5 StampedLock踩坑记
    • 案例
  • 6 总结
    • StampedLock读模板
    • StampedLock写模板
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档