1 简介

  StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。在原先读写锁的基础上新增了一种叫乐观读(Optimistic Reading)的模式。该模式并不会加锁,所以不会阻塞线程,会有更高的吞吐量和更高的性能。

  ReentrantReadWriteLock介绍 :https://www.cnblogs.com/jthr/p/16179865.html

 

2 特性及注意事项

2.1 三种访问数据模式

  1)Writing(独占写锁):writeLock 方法会使线程阻塞等待独占访问,同一时刻有且只有一个写线程获取锁资源,功能和ReentrantReadWriteLock 的读类似,

  2)Reading(悲观读锁):readLock方法,读读共享,读写互斥。功能和ReentrantReadWriteLock的写锁类似

  3)Optimistic Reading(乐观读):Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式。

 

2.2 支持读写锁相互转换

  ReentrantReadWriteLock 当线程获取写锁后可以降级成读锁,但是反过来则不行。

  StampedLock提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。

 

2.3 注意事项

  1)StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)

  2)StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。

  3)所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;

  4)所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;

  5)使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()

 

StampedLockReentrantReadWriteLock效率高

  关键在于StampedLock 提供的乐观读,我们知道ReentrantReadWriteLock 支持多个线程同时获取读锁,但是当多个线程同时读的时候,所有的写线程都是阻塞的。

  StampedLock 的乐观读允许一个写线程获取写锁,所以不会导致所有写线程阻塞,也就是当读多写少的时候,写线程有机会获取写锁,减少了线程饥饿的问题,吞吐量大大提高。同时允许多个乐观读和一个先线程同时进入临界资源操作,那读取的数据可能是错的怎么办?

  是的,乐观读不能保证读取到的数据是最新的,所以将数据读取到局部变量的时候需要通过 lock.validate(stamp) 校验下是否被写线程修改过,若是修改过则需要上悲观读锁,再重新读取数据到局部变量。

  同时由于乐观读并不是锁,所以没有线程唤醒与阻塞导致的上下文切换,性能更好。

 

4 示例

public class StanpedlockLockTest1 {

    private double x, y;
    private final StampedLock sl = new StampedLock();

    //写锁-排他锁
    void move(double deltaX, double deltaY) { // an exclusively locked method
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }


    //下面看看乐观读锁案例
    double distanceFromOrigin() { // A read-only method
        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);
    }

    //下面是悲观读锁转写锁
    void moveIfAtOrigin(double newX, double newY) { // upgrade
        // Could instead start with optimistic, not read mode
        long stamp = sl.readLock();
        try {
            while (true) { 
                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); //释放读锁或写锁
        }
    }
}

 

5 使用场景

  对于读多写少的高并发场景 StampedLock的性能很好,通过乐观读模式很好的解决了写线程“饥饿”的问题,我们可以使用StampedLock 来代替ReentrantReadWriteLock