StampedLock读写锁性能之王
一、序言
StampedLock
是Java 8新引入的高效读写锁。StampedLock实现了不仅多个读不互相阻塞,同时在读操作时不会阻塞写操作。
核心思想在于,在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。这种模式也就是典型的无锁编程思想,和CAS自旋的思想一样。这种操作方式决定了StampedLock在读线程非常多而写线程非常少的场景下非常适用,同时还避免了写饥饿情况的发生。
二、读写锁
StampedLock相较于普通读写锁,增加了一种乐观读。
(一)写锁
涉及对共享资源的修改,使用写锁-独占操作
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
当通过写锁持有锁时,普通共享读锁被互斥,需要等待写锁释放锁后方能申请锁。
(二)共享读锁
共享读锁是能够被多个线程同时持有,相较于读锁是共享的,相较于写锁是互斥的。
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) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
当读线程非常多时,申请写锁的线程可能一直被互斥,无法获取到锁,长时间无法写入数据。
此时可以先申请读锁,然后转化为写锁,完成数据写入。
(三)乐观读锁
使用乐观读锁,拷贝共享资源到本地方法栈中,如果有写锁被占用,可能造成数据不一致,所以要切换到普通读锁模式。
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
乐观读锁在持有锁期间,允许写锁同时申请锁,乐观读锁会对stamp
进行验证,假如有些线程发生,那么重新申请普通共享读锁。普通共享读锁在持有期间,写锁被互斥等待。
三、锁的性质
1、公平性
StampedLock是非公平锁,无底层机制保证读线程与写线程之间公平获得锁。考虑到读线程与读线程之间能够共享持有读锁,因此无公平性一说。
2、重入性
StampedLock是非重入锁。
3、乐观(悲观)性
乐观读锁属于乐观锁,先假设在持有锁期间无线程发生写操作,如果没有,则快速完成读数据操作,否则重新申请普通读锁,再次尝试读数据。
普通读锁属于悲观锁,当有线程持有普通读锁时,不允许直接申请到写锁。
写锁属于悲观锁。
喜欢本文点个♥️赞♥️支持一下,如有需要,可通过微信
dream4s
与我联系。相关源码在GitHub,视频讲解在B站,本文收藏在博客天地。