Java并发编程之StampedLock

33 阅读2分钟

简介

StampedLockjdk1.8中新增的一个类,是一种根据时间戳实现的锁机制,主要作用是为了提升读操作的性能。基本原理是加读锁、写锁都会生成一个时间戳,而在读取数据之前可以通过验证时间戳的方式来判断中途是否加了写锁,如果没有其他线程加写锁,那就可以直接读取数据而不用读锁了,这就是乐观读

使用

下面通过一个读读操作和读写操作分别演示下StampedLock的使用步骤及效果

@Slf4j
class StampedLockData {
    private final StampedLock lock = new StampedLock();
    private int data;
    public StampedLockData(int data) {
        this.data = data;
    }
    // 读操作
    public int read() {
        long stamp = lock.tryOptimisticRead();
        log.info("乐观读 {}", stamp);
        sleep(1000);
        if (lock.validate(stamp)) {
            log.info("验证时间戳成功 {}", stamp);
            return data;
        }
        log.info("升级成读锁 {}", stamp);
        try {
            stamp = lock.readLock();
            return data;
        } finally {
            log.info("释放读锁 {}", stamp);
            lock.unlock(stamp);
        }
    }
    // 写操作
    public void write(int newData) {
        long stamp = lock.writeLock();
        log.info("加写锁 {}", stamp);
        try {
            sleep(2000);
            this.data = newData;
        } finally {
            log.info("释放写锁 {}", stamp);
            lock.unlock(stamp);
        }
    }
    // 睡眠
    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

上面定义了一个StampedLockData类用来对其中的data属性进行共享操作,StampedLock类型的属性用来加读写锁。

read方法用来读取data的值,开始先尝试乐观读生成一个时间戳stamp,如果stamp验证成功了就直接返回data,如果失败了就升级为加读锁再读取data返回。

write方法是对data进行写操作,首先加写锁writeLock,然后再修改data的值。

sleep方法进行睡眠,主要是为了模拟出在乐观读的过程中如果有其他线程加了写锁的效果。

读读操作
public static void main(String[] args) {
    StampedLockData stampedLockData = new StampedLockData(0);
    new Thread(() -> {
        stampedLockData.read();
    }, "t1").start();
    new Thread(() -> {
        stampedLockData.read();
    }, "t2").start();
}

读读结果.png 两个线程同时执行read方法时,都是执行的乐观读并没有升级成读锁,而且时间戳也没有发生改变,验证了读读并发

读写操作
public static void main(String[] args) {
    StampedLockData stampedLockData = new StampedLockData(0);
    new Thread(() -> {
        stampedLockData.read();
    }, "t1").start();
    new Thread(() -> {
        stampedLockData.write(10);
    }, "t2").start();
}

读写结果2.png 两个线程分别进行读、写操作,可以看到t1在乐观读之后t2加了写锁,此时时间戳变成了384,所以时间戳验证失败了,t1就升级成了读锁,并且在t2释放写锁之后t1才获取到读锁执行任务,最后释放掉读锁,验证了读写互斥

优缺点

StampedLock的优点是在读操作多的情况下可以提高并发效率,缺点是不支持条件变量重入