Java并发(8)- 读写锁中的性能之王:StampedLock (3)

悲观读锁的获取和ReentrantReadWriteLock类似,不同在于StampedLock的读锁很容易溢出,最大只有127,超过后通过一个额外的变量readerOverflow来存储,这是为了给写锁留下更大的空间,因为写锁是在不停增加的。悲观读锁获取分下面四种情况:

没有读锁和写锁时,state为0001 0000 0000
// 小于 0000 0111 1110,可以尝试获取读锁
0001 0000 0000 & 0000 1111 1111 = 0000 0000 0000

有一个读锁时,state为0001 0000 0001
// 小于 0000 0111 1110,可以尝试获取读锁
0001 0000 0001 & 0000 1111 1111 = 0000 0000 0001

有一个写锁,state为0001 1000 0000
// 大于 0000 0111 1110,不可以获取读锁
0001 1000 0000 & 0000 1111 1111 = 0000 1000 0000

读锁溢出,state为0001 0111 1110
// 等于 0000 0111 1110,不可以获取读锁
0001 0111 1110 & 0000 1111 1111 = 0000 0111 1110
读锁的释放过程在没有溢出的情况下是通过s - RUNIT操作也就是-1来释放的,当溢出后则将readerOverflow变量-1。

乐观读锁的获取和验证

乐观读锁因为实际上没有获取过锁,所以也就没有释放锁的过程,只是在操作后通过验证检查和获取前的变化。源码如下:

//尝试获取乐观锁 public long tryOptimisticRead() { long s; return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L; } //验证乐观锁获取之后是否有过写操作 public boolean validate(long stamp) { //该方法之前的所有load操作在内存屏障之前完成,对应的还有storeFence()及fullFence() U.loadFence(); return (stamp & SBITS) == (state & SBITS); //比较是否有过写操作 }

乐观锁基本原理就时获取锁时记录state的写状态,然后在操作完成之后检查写状态是否有变化,因为写状态每次都会在高位留下记录,这样就避免了写锁获取又释放后得不到准确数据。获取写锁记录有三种情况:

没有读锁和写锁时,state为0001 0000 0000
//((s = state) & WBIT) == 0L) true
0001 0000 0000 & 0000 1000 0000 = 0000 0000 0000
//(s & SBITS)
0001 0000 0000 & 1111 1000 0000 = 0001 0000 0000

有一个读锁时,state为0001 0000 0001
//((s = state) & WBIT) == 0L) true
0001 0000 0001 & 0000 1000 0000 = 0000 0000 0000
//(s & SBITS)
0001 0000 0001 & 1111 1000 0000 = 0001 0000 0000

有一个写锁,state为0001 1000 0000
//((s = state) & WBIT) == 0L) false
0001 1000 0000 & 0000 1000 0000 = 0000 1000 0000
//0L
0000 0000 0000

验证过程中是否有过写操作,分四种情况

写过一次
0001 0000 0000 & 1111 1000 0000 = 0001 0000 0000
0010 0000 0000 & 1111 1000 0000 = 0010 0000 0000 //false

未写过,但读过
0001 0000 0000 & 1111 1000 0000 = 0001 0000 0000
0001 0000 1111 & 1111 1000 0000 = 0001 0000 0000 //true

正在写
0001 0000 0000 & 1111 1000 0000 = 0001 0000 0000
0001 1000 0000 & 1111 1000 0000 = 0001 1000 0000 //false

之前正在写,无论是否写完都不会为0L
0000 0000 0000 & 1111 1000 0000 = 0000 0000 0000 //false

性能测试

分析完了StampedLock的实现原理,这里对StampedLock、ReentrantReadWriteLock以及Synchronized分别在各种场景下进行性能测试,测试的基准代码采用https://blog.takipi.com/java-8-stampedlocks-vs-readwritelocks-and-synchronized/ 文章中的代码,首先贴出上述博客中的测试结果,文章中的OPTIMISTIC模式由于采用了“脏读”模式,这里不采用OPTIMISTIC的测试结果,只比较StampedLock、ReentrantReadWriteLock以及Synchronized。

5个读线程和5个写线程场景:表现最好的是StampedLock的正常模式以及ReentrantReadWriteLock。

Java并发(8)- 读写锁中的性能之王:StampedLock


10个读线程和10个写线程场景:表现最好的是StampedLock的正常模式以及Synchronized。

Java并发(8)- 读写锁中的性能之王:StampedLock


16个读线程和4个写线程场景:表现最好的是StampedLock的正常模式以及Synchronized。

Java并发(8)- 读写锁中的性能之王:StampedLock


19个读线程和1个写线程场景:表现最好的是Synchronized。

Java并发(8)- 读写锁中的性能之王:StampedLock


博客评论中还有一种测试场景2000读线程和1个写线程,测试结果如下:
StampedLock ... 12814.2 ReentrantReadWriteLock ... 18882.8 Synchronized ... 22696.4
表现最好的是StampedLock。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zydggf.html