基于AQS 的 ReadWriteLock

  • 引用自JDK文档的简介

ReadWriteLock 维护了一对锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。

所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。

与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。

  • 类关系图

  • 结合关系图来看ReentrantReadWriteLock类
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {

    private static final long serialVersionUID = -6992448646407690164L;
    /** 内部类 readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 内部类 writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** 同步机制类 */
    final Sync sync;
    
    // 省略 。。。。。。
    
    // 初始化
    public ReentrantReadWriteLock() {
        this(false);
    }
    //默认是非公平实现
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    // 省略 。。。。。。
}
public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;
    // 读锁的实现依赖于ReentrantReadWriteLock中的sync
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquireShared(1);
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryReadLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.releaseShared(1);
    }

    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

    public String toString() {
        int r = sync.getReadLockCount();
        return super.toString() + "[Read locks = " + r + "]";
    }
}
public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;
    // 写锁的实现依赖于ReentrantReadWriteLock中的sync
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquire(1);
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock( ) {
        return sync.tryWriteLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ? "[Unlocked]" : "[Locked by thread " + o.getName() + "]");
    }

    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    public int getHoldCount() {
        return sync.getWriteHoldCount();
    }
}
  • ReadLock和WriteLock这两个类都实现了Lock接口,从源码中可以看出,读锁、写锁的操作都是依靠NonfairSync类来实现的,NonfairSync继承自Sync,Sync又继承自AbstractQueuedSynchronizer。
  • 我们都知道基于AQS的一系列锁的实现都与CAS计算status值有关,那ReadWriteLock分为读写两种类型,如何用一个int型的变量来记录同步状态呢???
  • 读锁和写锁共用一个int型的status字段,作者使用"按位切割"来共用一个字段,在java中int是32位的二进制表示,所以共享读锁用32位中的前16位,独占写锁用32位中的后16位具体是如何分割?如何计算?如何统计数量?在看过源码之后再具体介绍
  • Sync中比较重要的几个变量
static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** 获取共享锁的数量  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** 获取独占锁的数量  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 尝试获取读共享锁
protected final int tryAcquireShared(int unused) {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取status当前同步状态值
    int c = getState();
    // 判断是否有写独占锁 && 获取写独占锁的线程是不是当前线程
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        // 说明当前存在写独占锁,读共享锁获取失败直接返回
        return -1;
    // 获取共享锁的数量
    int r = sharedCount(c);
    // 判断读锁是否要阻塞 && 当前共享锁数量是否大于最大数量 && CAS改变同步状态是否成功
    if (!readerShouldBlock() && 
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            // 记录没有共享读锁的首个线程
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // 首个共享读锁重入,数量自增1
            firstReaderHoldCount++;
        } else {
            // 将当前共享读锁的数量绑定到ThreadLocal中
            // HoldCounter类只有两个字段 { 线程读锁计数, 读锁线程id }
            // readHolds是ThreadLocalHoldCounter类型继承自ThreadLocal
            // 所有共享读线程的HoldCounter对象绑定在ThreadLocal中
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                // readHolds.get()获取来的是一个初始的HoldCounter实例
                // cachedHoldCounter 是缓存的一个成员HoldCounter类实例
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                // rh不等于null && count == 0
                readHolds.set(rh);
            // 自增
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
// Sync初始化时构造ThreadLocalHoldCounter实例
Sync() {
    readHolds = new ThreadLocalHoldCounter();
    setState(getState()); // ensures visibility of readHolds
}

static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    // ThreadLocal初始化时会调用子类实现方法initialValue,将HoldCounter实例存入ThreadLocal
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}
// 尝试获取写独占锁
protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取同步状态
    int c = getState();
    // 获取独占写锁数量
    int w = exclusiveCount(c);
    if (c != 0) {
        // if c != 0 and w == 0 then shared count != 0
        // status!=0说明有线程获取了锁
        // w=0说明获取的锁不是写锁
        // 那么则说明读锁已被获取,有任何一把锁的情况下都要阻塞
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 判断写锁是否超过最大数量
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 如果是同一线程重入锁的话,修改同步状态中的数量
        setState(c + acquires);
        return true;
    }
    // c = 0 说明没有任何锁,尝试CAS(status)
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 设置独占锁对应线程为自己
    setExclusiveOwnerThread(current);
    return true;
}
  • SHARED_SHIFT = 16
  • exclusiveCount() 讲解
// 获取独占写锁数量
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
status & (1 << SHARED_SHIFT) - 1;
如果读锁与写锁同时存在,如下
# 注意:这里只是举个例子,假如存在这种情况,为了方便讲解同步状态
0000 0000 0000 0001 0000 0000 0000 0001
说明有1个读锁1个写锁
当程序要获取写独占锁锁时
因为0&1=0,1&1=1,所以
0000 0000 0000 0001 0000 0000 0000 0001
&
0000 0000 0000 0000 ‭1111 1111 1111 1111‬
=
0000 0000 0000 0000 0000 0000 0000 0001
即只有1个独占写锁,存在共享读锁时并不影响获取独占写锁,前16位与后16位完全不相关。
  • sharedCount() 讲解
// 获取共享读锁数量
// >>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
// >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0。
// 对于正数而言,>>和>>>没区别,左移没有<<<运算符
获取共享锁数量static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
c右移16位是为了获取前16位
例如只有1个共享锁时
二进制为 0000 0000 0000 0001 0000 0000 0000 0000‬
十进制为 65536
二进制右移16位 0000 0000 0000 0000‬ 0000 0000 0000 0001
右移运算后结果为1,得到1个共享读锁
  • readLock tryAcquireShared 更新status时,compareAndSetState(c, c + SHARED_UNIT) 讲解
readLock tryAcquireShared 更新status时,将当前status+(1 << SHARED_SHIFT)是因为什么
因为读锁用32位中的前16位,写锁用32位中的后16位
所以tryAcquireShared方法中更新status变量必须写为当前status+(1 << SHARED_SHIFT)
1左移16位运算得65536(2的16次方),对应32位二进制是‭0000 0000 0000 0001 0000 0000 0000 0000‬
status+65536的目的是只操作32位中的前16位
此时的status=65536,那么第二线程获取读锁后就变成了65536+65536(其实就是前16位的1+1操作),以此类推
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值