一、StampedLock概述

StampedLock 是读写锁的实现,对比 ReentrantReadWriteLock 主要不同是该锁不允许重入,多了乐观读的功能,使用上会更加复杂一些,但是具有更好的性能表现。StampedLock 的状态由版本和读写锁持有计数组成。 获取锁方法返回一个邮戳,表示和控制与锁状态相关的访问; 这些方法的“尝试”版本可能会返回特殊值 0 来表示获取锁失败。 锁释放和转换方法需要邮戳作为参数,如果它们与锁的状态不匹配则失败。本文描述 StampedLock 的基础使用场景,入门教程。

二、读写案例

在案例中,先使用 tryOptimisticRead 获取锁的邮戳,进行值修改后通过 validate 校验锁版本是否正确,此时并没有进行锁获取,这是一种乐观的模式。如果校验发现锁版本已被修改,则可以通过 readLock 以获取共享锁,然后重新进行数据读取。

package com.nineya.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {
    private int num;
    private int numLock;
    StampedLock lock = new StampedLock();

    public int getNum() {
        return num;
    }

    public void read() {
        int a = getNum();
        int b = getNum();
        if (a != b) {
            System.out.println("num 改变: a = " + a + ", b = " + b);
        }
    }

    public void write() {
        num++;
    }

    public int getNumLock() {
        return numLock;
    }

    public void readLock(){
        long s = lock.tryOptimisticRead();
        int a = getNumLock();
        int b = getNumLock();
        if (!lock.validate(s)) {
            long state = lock.readLock();
            a = getNumLock();
            b = getNumLock();
            lock.unlockRead(state);
        }
        if (a != b) {
            System.out.println("numLock 改变: a = " + a + ", b = " + b);
        }
    }

    public void writeLock() {
        long state = lock.writeLock();
        numLock++;
        lock.unlockWrite(state);
    }

    public void print() {
        System.out.println("num = " + num + ", numLock = " + numLock);
        num = 0;
        numLock = 0;
    }

    public static void main(String[] args) throws InterruptedException {
        StampedLockTest main = new StampedLockTest();
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int j = 0; j < 5; j++) {
            for (int i = 0; i < 1000; i++) {
                executor.execute(main::write);
                executor.execute(main::writeLock);
                executor.execute(main::read);
                executor.execute(main::readLock);
            }
            Thread.sleep(1000);
            main.print();
        }
    }
}

程序运行结果如下:

num 改变: a = 63, b = 64
num 改变: a = 92, b = 93
num 改变: a = 535, b = 536
num 改变: a = 939, b = 940
num = 999, numLock = 1000
num 改变: a = 134, b = 135
num = 998, numLock = 1000
num = 998, numLock = 1000
num = 999, numLock = 1000
num = 999, numLock = 1000

三、所有接口方法

方法 说明
long writeLock() 获取独占锁,如果该锁被另一个线程保持,则阻塞线程,直到拿到锁并返回邮戳。
long tryWriteLock() 尝试获取独占锁,非公平,不阻塞线程。如果获取锁成功则返回邮戳,否则返回 0。
long tryWriteLock(long time, TimeUnit unit) throws InterruptedException 在给定的等待时间内尝试获取独占锁,获取成功则立即返回邮戳。超过等待时间获取失败或者接收到中断信号则返回 0。
long writeLockInterruptibly() throws InterruptedException 调用后一直阻塞到获得独占锁,但是接受中断信号。
long readLock() 获取共享锁,如果该锁被另一个线程保持,则阻塞线程,直到拿到锁并返回邮戳。
long tryReadLock() 尝试获取共享锁,非公平,不阻塞线程。如果获取锁成功则返回邮戳,否则返回 0。
tryReadLock(long time, TimeUnit unit) throws InterruptedException 在给定的等待时间内尝试获取共享锁,获取成功则立即返回邮戳。超过等待时间获取失败或者接收到中断信号则返回 0。
long readLockInterruptibly() throws InterruptedException 调用后一直阻塞到获得共享锁,但是接受中断信号。
long tryOptimisticRead() 如果当前未持有独占锁则返回当前锁版本作为邮戳,用于在以后验证状态,如果已持有独占锁则返回0,用于乐观锁。
boolean validate(long stamp) 如果自给定邮戳获取后未获取过独占锁(独占锁状态码未改变),则返回 true。 如果状态为 0,则始终返回 false。
void unlockWrite(long stamp) 如果锁状态与给定的邮戳匹配,则释放独占锁。
void unlockRead(long stamp) 如果锁状态与给定的邮戳匹配,则释放共享锁。
void unlock(long stamp) 如果锁状态与给定的邮戳匹配,则释放锁的相应模式。
long tryConvertToWriteLock(long stamp) 验证当前锁版本和锁持有状态和给定的邮戳是否匹配,如果不匹配、邮戳的锁状态有误或当前持有多个共享锁则返回 0。匹配时则分三种情况,当前未持有锁则获取独占锁,当前持有独占锁则不进行操作,当前仅持有一个共享锁则释放共享锁获取独占锁,最终返回独占锁的邮戳。
long tryConvertToReadLock(long stamp) 验证当前锁版本和锁持有状态和给定的邮戳是否匹配,如果不匹配或者邮戳的锁状态有误则返回 0。匹配时则分三种情况,当前未持有锁则获取共享锁,当前持有独占锁则释放独占锁获取共享锁,当前持有共享锁则不进行操作,最终返回共享锁的邮戳。
long tryConvertToOptimisticRead(long stamp) 验证当前锁版本和锁持有状态和给定的邮戳是否匹配,如果匹配则进行一次锁释放,如果不匹配或者邮戳的锁状态有误则返回 0。该方法的逻辑和 unlock 方法的逻辑相似,如果当前未持有锁就直接返回锁版本,如果持有锁则进行一次锁释放,再返回锁版本。
boolean tryUnlockWrite() 如果持有独占锁则释放锁,不需要邮戳。 此方法对于错误后的恢复可能很有用。
boolean tryUnlockRead() 如果持有共享锁则进行一次锁释放,不需要邮戳。 此方法对于错误后的恢复可能很有用。
boolean isWriteLocked() 如果当前持有独占锁,则返回true 。
boolean isReadLocked() 如果当前持有共享锁,则返回true 。
int getReadLockCount() 查询当前持有的共享锁计数。 该方法设计用于监视系统状态,而不是用于同步控制。
Lock asReadLock() 返回此 StampedLock 的普通Lock视图,其中Lock.lock方法映射到readLock ,其他方法也类似。 返回的 Lock 不支持Condition ; 方法Lock.newCondition()抛出UnsupportedOperationException。
Lock asWriteLock() 返回此 StampedLock 的普通Lock视图,其中Lock.lock方法映射到writeLock ,其他方法也类似。 返回的 Lock 不支持Condition ; 方法Lock.newCondition()抛出UnsupportedOperationException。
ReadWriteLock asReadWriteLock() 返回此 StampedLock 的ReadWriteLock视图,其中ReadWriteLock.readLock()方法映射到asReadLock() , ReadWriteLock.writeLock()映射到asWriteLock() 。