首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 18 | StampedLock:有没有比读写锁更快的锁? 在Java并发编程的广阔领域中,锁是实现线程同步和数据一致性保护的关键机制之一。随着多线程应用对性能要求的日益提升,选择合适的锁策略成为了优化程序性能的重要方面。Java并发包(`java.util.concurrent`)提供了多种锁机制,如`ReentrantLock`、`ReadWriteLock`等,它们各自适用于不同的并发场景。然而,在追求极致性能的道路上,Java 8 引入了一种新的锁机制——`StampedLock`,它旨在提供一种比传统读写锁更快但使用上更为复杂的锁机制。 #### 一、`StampedLock`简介 `StampedLock`是Java 8中新增的一个类,它提供了一种基于能力(capability)的锁机制。与传统的读写锁(如`ReentrantReadWriteLock`)相比,`StampedLock`通过允许读操作在大多数情况下无锁进行(即乐观读),从而提高了性能。这种设计思想类似于无锁编程中的CAS(Compare-And-Swap)操作,但`StampedLock`提供了更高级的锁管理能力,同时保持了相对简单的API。 #### 二、`StampedLock`的核心概念 `StampedLock`的核心在于它返回的“stamp”(戳记)。每个锁状态(无论是读锁还是写锁)都通过一个唯一的stamp值来表示,这个stamp值作为后续解锁操作的凭证。当线程尝试获取锁时,`StampedLock`会返回一个stamp值,表示该线程已成功获取了相应的锁。当线程完成操作并希望释放锁时,它必须提供之前获得的stamp值作为解锁的凭证。 #### 三、`StampedLock`的锁模式 `StampedLock`支持三种基本的锁模式: 1. **写锁(Exclusive Lock)**: 写锁是排他的,即在同一时刻,只有一个线程可以持有写锁。持有写锁的线程可以安全地修改受保护的数据,而无需担心其他线程同时访问。 2. **悲观读锁(Pessimistic Read Lock)**: 悲观读锁与传统读写锁中的读锁相似,它在获取锁时会阻塞其他写锁的申请,但不会阻塞其他读锁的申请。这种模式适用于读多写少的场景,但相比乐观读,它可能会引入更多的锁竞争。 3. **乐观读(Optimistic Read)**: 乐观读是`StampedLock`最独特的特性之一。它允许读操作在大多数情况下无锁进行,通过验证数据在读取期间未被修改来确保数据一致性。如果数据在读取过程中被修改,则乐观读会失败,并需要重试或转换为悲观读。 #### 四、`StampedLock`的性能优势 `StampedLock`相比传统读写锁的主要性能优势在于其乐观读模式。在并发环境中,读操作往往远多于写操作,而乐观读能够在不引入额外锁开销的情况下执行,从而显著提高程序的吞吐量。然而,这种性能提升并非无代价的,它要求开发者在编写代码时更加小心,确保能够正确处理乐观读失败的情况。 #### 五、使用`StampedLock`的注意事项 尽管`StampedLock`提供了性能上的优势,但其使用也伴随着一些挑战和限制: 1. **不支持重入**: `StampedLock`不支持锁的重入。如果一个线程已经持有了读锁或写锁,它不能再次尝试获取任何类型的锁(无论是读锁还是写锁),否则会导致死锁。这一限制要求开发者在编写代码时需要更加注意锁的获取和释放逻辑。 2. **乐观读的风险**: 乐观读虽然性能高,但并非总是安全的。如果多个线程频繁地修改共享数据,乐观读可能会频繁失败,导致性能下降。此外,乐观读还需要开发者在代码中显式地处理数据一致性问题,增加了编程的复杂性。 3. **中断不敏感**: 与`ReentrantLock`等可中断锁不同,`StampedLock`的获取锁操作是不响应中断的。这意味着,如果一个线程在等待锁时被中断,它将无法立即响应中断,而是会继续等待直到锁被释放或操作超时。 4. **转换锁的复杂性**: 在某些情况下,可能需要将乐观读转换为悲观读或写锁。这种转换需要额外的逻辑来确保数据的一致性和线程的安全性,增加了代码的复杂性。 #### 六、`StampedLock`的应用场景 `StampedLock`最适合用于读多写少、且读操作对性能要求极高的场景。例如,在缓存系统、数据聚合或报告生成等应用中,读操作往往远多于写操作,且对数据的实时性要求不高。在这些场景下,使用`StampedLock`可以显著提高程序的吞吐量,减少锁竞争带来的性能开销。 #### 七、示例代码 下面是一个使用`StampedLock`的简单示例,展示了如何在一个共享数据上实现读写操作: ```java import java.util.concurrent.StampedLock; public class DataStructure { private final StampedLock lock = new StampedLock(); private int value = 0; public void increment() { long stamp = lock.writeLock(); // 获取写锁 try { value++; } finally { lock.unlock(stamp); // 释放写锁 } } public int getValueOptimistically() { long stamp = lock.tryOptimisticRead(); // 尝试获取乐观读锁 int current = value; if (!lock.validate(stamp)) { // 检查锁是否被其他线程获取 stamp = lock.readLock(); // 转换为悲观读锁 try { current = value; } finally { lock.unlockRead(stamp); // 释放悲观读锁 } } return current; } } ``` #### 八、总结 `StampedLock`作为Java 8中引入的一种新型锁机制,通过提供乐观读模式,在特定场景下能够显著提升程序的性能。然而,其使用也伴随着一定的挑战和限制,要求开发者在编写代码时需要更加谨慎和细致。在选择是否使用`StampedLock`时,需要根据具体的应用场景和性能需求进行权衡和考虑。通过合理使用`StampedLock`,可以在保持数据一致性的同时,实现更高的并发性能和吞吐量。
上一篇:
17 | ReadWriteLock:如何快速实现一个完备的缓存?
下一篇:
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
该分类下的相关小册推荐:
Java语言基础5-面向对象初级
SpringBoot零基础到实战
Java性能调优实战
SpringBoot合辑-高级篇
手把手带你学习SpringBoot-零基础到实战
Java语言基础2-运算符
JAVA 函数式编程入门与实践
Java语言基础7-Java中的异常
Java必知必会-Maven高级
Java语言基础10-Java中的集合
深入理解Java虚拟机
Java语言基础4-数组详解