StampedLock 是 Java 8 引入的一种高级的锁机制,它位于 java.util.concurrent.locks 包中。与传统的读写锁(ReentrantReadWriteLock)相比,StampedLock 提供了更灵活和更高性能的锁解决方案,尤其适用于读操作远多于写操作的场景。

1.特点展示

相比于 Java 中的其他锁,StampedLock 具有以下特点:

  1. 读写分离:StampedLock 支持读写分离,读锁和写锁可以同时被不同的线程持有,从而提高了并发性能。而 synchronized 和 ReentrantLock 则不支持读写分离,读操作和写操作会相互阻塞。
  2. 乐观读:StampedLock 支持乐观读,读操作不会阻塞写操作,只有在写操作发生时才会升级为悲观读。这种方式适用于读多写少的场景,可以提高读操作的并发性能。而 ReentrantReadWriteLock 则不支持乐观读。
  3. 不可重入:ReentrantLock 和 synchronized 都是可重入锁,而 StampedLock 的写锁是不可重入的。
  4. 性能优势:StampedLock 在多线程并发中的读多情况下有更好的性能,因为 StampedLock 获取乐观读锁时,不需要通过 CAS 操作来设置锁的状态,只是简单地通过测试状态即可。

2.基本使用

StampedLock 有三种读写方法:

  1. readLock:读锁,用于多线程并发读取共享资源。
  2. writeLock:写锁,用于独占写入共享资源。
  3. tryOptimisticRead:读乐观锁,用于在不阻塞其他线程的情况下尝试读取共享资源。

其中 readLock() 和 writeLock() 方法与 ReentrantReadWriteLock 的用法类似,而 tryOptimisticRead() 方法则是 StampedLock 引入的新方法,它用于非常短的读操作。

因此,我们在加锁时,可以使用性能更高的读乐观锁来替代传统的读锁,如果能加锁成功,则它可以和其他线程(即使是写操作)一起执行,也无需排队运行(传统读锁遇到写锁时需要排队执行),这样的话 StampedLock 的执行效率就会更高,它是使用如下:

// 创建 StampedLock 实例
StampedLock lock = new StampedLock();
// 获取乐观读锁
long stamp = lock.tryOptimisticRead();
// 读取共享变量
if (!lock.validate(stamp)) { // 检查乐观读锁是否有效
stamp = lock.readLock(); // 如果乐观读锁无效,则获取悲观读锁
try {
// 重新读取共享变量
} finally {
lock.unlockRead(stamp); // 释放悲观读锁
}
} // 获取悲观读锁
long stamp = lock.readLock();
try {
// 读取共享变量
} finally {
lock.unlockRead(stamp); // 释放悲观读锁
} // 获取写锁
long stamp = lock.writeLock();
try {
// 写入共享变量
} finally {
lock.unlockWrite(stamp); // 释放写锁
}

使用乐观读锁的特性可以提高读操作的并发性能,适用于读多写少的场景。如果乐观读锁获取后,在读取共享变量前发生了写入操作,则 validate 方法会返回 false,此时需要转换为悲观读锁或写锁重新访问共享变量。

3.注意事项

在使用 StampedLock 时,需要注意以下几个问题:

  1. 不可重入性:StampedLock 的读锁和写锁都不支持重入,这意味着一个线程在获取了锁之后,不能再次获取同一个锁,所以在使用 StampedLock 时,一定要避免嵌套使用。
  2. 死锁问题:使用 StampedLock 时,必须使用与获取锁时相同的 stamp 来释放锁,否则就会导致释放锁失败,从而导致死锁问题的发生。
  3. CPU 使用率飙升问题:如果 StampedLock 使用不当,具体来说,在 StampedLock 执行 writeLock 或 readLock 阻塞时,如果调用了中断操作,如 interrupt() 可能会导致 CPU 使用率飙升。这是因为线程接收到了中断请求,但 StampedLock 并没有正确处理中断信号,那么线程可能会陷入无限循环中,试图从中断状态中恢复,这可能会导致 CPU 使用率飙升。

4.CPU 100%问题演示

以下代码中线程 2 会导致 CPU 100% 的问题,如下代码所示:

public void runningTask() throws Exception{
final StampedLock lock = new StampedLock();
Thread thread = new Thread(()->{
// 获取写锁
lock.writeLock();
// 永远阻塞在此处,不释放写锁
LockSupport.park();
});
thread.start(); // 保证 thread 获取写锁
Thread.sleep(100);
Thread thread2 = new Thread(()->
// 阻塞在悲观读锁
lock.readLock()
);
thread2.start();
// 保证 thread2 阻塞在读锁
Thread.sleep(100);
// 中断线程 thread2,导致 thread2 CPU 飙升
thread2.interrupt();
thread2.join();
}

以上代码中,线程一先获取到锁,之后阻塞,并未释放锁,而线程二阻塞在 readLock() 读锁时,收到了中断请求 interrupt(),但并未正确处理中断异常,因此线程会陷入无限循环中,试图从中断状态中恢复,这就会导致 CPU 使用率一直飙升。

课后思考

如何避免 StampedLock CPU 100% 的问题?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

为什么StampedLock会导致CPU100%?的更多相关文章

  1. 并发场景下HashMap死循环导致CPU100%的问题

    参考链接:并发场景下HashMap死循环导致CPU100%的问题

  2. 黄聪:Wordpress程序Mysql查询导致CPU100%,页面错误增量飙高解决方案

    最近做的网站,访客一多,CPU就会飙到100%,找了网上很多解决方案,用了下面的方法进行优化: 1.mysql开启慢查询方法: 在my.ini的[mysqld]添加如下语句: log-slow-que ...

  3. php用户量剧增导致cpu100%解决办法

    在php扩展里边开启opcache扩展,此扩展是解析php的缓存机制,每次解析都要消耗cpu,所以有大量的fpm进程去占用cpu,开启此扩展之后cpu就瞬间下来了,只解析第一次,往后的都使用缓存.很好 ...

  4. 利用windbg查找dictionary导致IIS占CPU100%案例分析(一)

    一.背景 先说下windbg使用场景.各位coder在工作中或多或少都会遇到下面四种情况 1.本地代码好好的,放服务器上运行一段时间后,IIS服务突然占用 w3wp.exe CPU突然100% ,不得 ...

  5. HashMap-死锁导致cpu占用100%分析(转)

    最近项目里面的一段千年代码出了问题,这个问题以前也出现过,不过不是那么明显,这次迁移机器由以前的4台机子变成2台以后问题被放大,最终不得不解决,特此分析一下. 先放出问题的代码 ? 1 2 3 4 5 ...

  6. 阿里云被挖矿使用,导致cpu长期处于100%,ddgs进程,xWx3T进程,关于redis密码

    1.使用top命令,查看到一个叫xWx3T的进程cpu占用99.8%,由于我的阿里云是单核的,所以最高只能100%. 把它用kill命令杀死后,过一会儿又启动了,又占用100%. 使用ps -ef可以 ...

  7. Dictionary导致IIS CPU 100%案例分析 学会使用WinDbg工具

    .NET  开发注意 线程安全性问题.弄不好可能会导致CPU满载 特别主要 Dictionary作为静态变量使用的情况. 解决方法: Dictionary 换成  ConcurrentDictiona ...

  8. 二:基础概述netty

    如果不了解netty的,可以百度下,netty社区现在也比较活跃. 现在所谓的大数据,flume,storm等底层都是netty.   netty的性能模型: io模型---->异步非阻塞io ...

  9. netty学习

    1.不选择java原生nio的原因   (1)nio的类库和api繁杂   (2)需要具备其他的额外的技能做铺垫,例如熟悉java多线程编程.   (3)可靠性能力补齐的工作量和难度都非常大,例如客户 ...

  10. Java IO编程全解(六)——4种I/O的对比与选型

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7804185.html 前面讲到:Java IO编程全解(五)--AIO编程 为了防止由于对一些技术概念和术语 ...

随机推荐

  1. Git 出现 Permission denied 时,重新生成ssh密钥

    1,右键本地仓库,点击  [ Git Bash Here] 出现如下对话框: 输入 ssh-keygen  然后一直回车,注意: 保存的SSH目录 在   C:\Users\xiaocj\.ssh  ...

  2. Ceph配置与认证授权

    目录 Ceph配置与认证授权 1. 为什么现在不采用修改配置文件的方式了呢? 2. Ceph元变量 3. 使用命令行修改配置 3.1 全部修改(使用服务名) 3.2 部分修改(修改进程) 3.3 临时 ...

  3. Linux中根据关键字获取某一行的行号

    [root@localhost ~]# cat test.txt 123213 ehualu.server ehualu.docker 10.0.0.10 ehualu.server ehualu.d ...

  4. redis 基础管理

    配置文件 优化redis配置文件定制 cat /nosql/redis/6379/redis.conf daemonize yes port 6379 logfile /nosql/redis/637 ...

  5. nginx获取后端真实IP,添加后端服务器响应时间并记录日志

    nginx获取后端真实IP,添加后端服务器响应时间并记录日志 1.日志定义 log_format nginx '$remote_addr - $remote_user [$time_local] &q ...

  6. vue devtools工具安装 Vue实现数据绑定的原理

    通过chrome中的谷歌插件商店安装Vue Devtools工具,此工具帮助我们进行vue数据调试所用,一定要安装. https://chrome.google.com/webstore?utm_so ...

  7. ABC335

    输 E 对于 \((u,v)\): 若 \(a_u = a_v\),则把 \(u\) 和 \(v\) 扔到同一个并查集里 否则连接两个点 然后跑一遍 dp 即可. code F 根号分治 如果 \(a ...

  8. golang sync.Once 保证某个动作仅执行一次的机制

    type Once struct { done atomic.Uint32 m Mutex } 这段代码是 Go 语言标准库中 sync 包的一部分,定义了一个 Once 类型.Once 类型用于确保 ...

  9. 使用 Promise.withResolvers() 来简化你将函数 Promise 化的实现~~

    引言 在JavaScript编程中,Promise 是一种处理异步操作的常用机制.Promise 对象代表了一个尚未完成但预期将来会完成的操作的结果.在本文中,我们将探讨如何通过使用 ES2024 的 ...

  10. Git配置环境变量

    由于学习需要装了git,使用终端查看版本号时 提示 'git' 不是内部或外部命令,也不是可运行的程序 或批处理文件. 原因 因为没有配置Git环境变量 解决方法:配置环境变量 开始菜单=>设置 ...