StampedLock读写锁性能之王

·  阅读 307
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站,本文收藏在博客天地


分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改