StampedLock 支持的三种锁模式 写锁、悲观读锁和乐观读
我们先来看看在使用上 StampedLock 和上一篇文章讲的 ReadWriteLock 有哪些区别。ReadWriteLock 支持两种模式:一种是读锁,一种是写锁。而 StampedLock 支持三种模式。其中写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似,允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的。不同的是:StampedLock 里的写锁和悲观读锁加锁成功之后,都会返回一个stamp;然后解锁的时候,需要传入这个 stamp。相关的示例代码如下。
final StampedLock sl = new StampedLock(); // 获取 / 释放悲观读锁 long stamp = sl.readLock(); try { // 省略业务相关代码..... } finally { sl.unlockRead(stamp); } // 获取 释放写锁 long stamp = sl.writeLock(); try { // 省略业务相关代码..... } finally { sl.unlockWrite(stamp); }
StampedLock 的性能之所以比 ReadWriteLock 还要好,其关键是 StampedLock 支持乐观读的方式。ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。注意这里,我们用的是“乐观读”这个词,而不是“乐观读锁”,是要提醒你,乐观读这个操作是无锁的,所以相比较 ReadWriteLock 的读锁,乐观读的性能更好一些。
在上面这个代码示例中,如果执行乐观读操作的期间,存在写操作,会把乐观读升级为悲观读锁。这个做法挺合理的,否则你就需要在一个循环里反复执行乐观读,直到执行乐观读操作的期间没有写操作(只有这样才能保证 x 和 y 的正确性和一致性),而循环读会浪费大量的 CPU。升级为悲观读锁,代码简练且不易出错,建议你在具体实践时也采用这样的方法。
现实使用中,读多写少的简单场景下,可以用stampedLock代替ReadWriteLock,但是StampedLock 的功能仅仅是 ReadWriteLock 的子集,在使用的时候,还是有几个地方需要注意一下
1:StampedLock 不支持重入
2:悲观读锁、写锁都不支持条件变量
3:StampedLock 的 readLock() 或者 writeLock()上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。
若需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。
文中部分来源:王宝令 《StampedLock:有没有比读写锁更快的锁》