java并发编程之ReentranReadWriteLock分析

上一篇文章讲了 ReentrantLock的实现,这片文章继续深入 讲讲ReentrantReadWritrLock的实现

首先回想ReentrantLock中自定义同步器的实现,同步状态(state)表示锁被一个线程重复获取的次数。同样ReentranReadWriteLock也用state来维护维护多个读线程和一个写线程的状态.那么是怎么用一个int变量来维护两种锁的状态呢?答案是 :“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写 。具体如下图:
在这里插入图片描述

那么读写锁是如何迅速确定读和写各自的状态呢?

答案:是通过位运算。假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是
S+0x00010000。
根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读
状态(S>>>16)大于0,即读锁已被获取 。

理解了按位切割来区分 读锁和写锁,那么在去理解ReentranReadWriteLock的加锁和写锁就会容易很多,

首先我们先看看ReentranReadWriteLock写锁的加锁和释放

ReentranReadWriteLock写锁的加锁和释放

这里我们主要看ReentranReadWriteLock重写AQS的tryAcquire方法(如果对这里不理解,可以先看看AQS的实现。它是整个Lock体系的关键。)

看ReentranReadWriteLock写锁的加锁前我们先看看 ReentrantLock公平锁的实现,(为什么要看公平锁呢?因为ReentranReadWriteLock的默认写锁就是公平锁);

   protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

对比,虽然代码有些不一样但是思路是一样的。都是判断当前锁是否可重入加锁

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    //拿到低16位写锁值
    int w = exclusiveCount(c);
    //如果有锁
    if (c != 0) {
       
        // (Note: if c != 0 and w == 0 then shared count != 0)
        //如果写锁为0或者 当前线程不是拥有锁的线程(这里是如果有其他线程拥有读锁,是不能加写锁) 返回false 
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //一直加锁但不释放锁会抛异常 MAX_COUNT是65535
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire 重进入锁
        setState(c + acquires);
        return true;
    }
     //没有锁,直接加写锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

这里需要注意:除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的
判断 ,如果存在读锁,则写锁不能被获取,原因在于:读写锁要确保写锁的操作对读锁可见,如
果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当
前写线程的操作 。

写锁的释放就和 ReentrantLock锁释放类似。这里就不加赘述!

ReentranReadWriteLock读锁的加锁和释放

获取读锁的实现从Java 5到Java 6之后变得复杂许多,主要原因是新增了一
些功能,例如getReadHoldCount()方法,作用是返回当前线程获取读锁的次数。读状态是所有线
程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由
线程自身维护,这使获取读锁的实现变得复杂。

源码如下:

        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }
        final int fullTryAcquireShared(Thread current) {
        HoldCounter rh = null;
        for (;;) {
            int c = getState();
            if (exclusiveCount(c) != 0) {
                if (getExclusiveOwnerThread() != current)
                    return -1;
                // else we hold the exclusive lock; blocking here
                // would cause deadlock.
            } else if (readerShouldBlock()) {
                // Make sure we're not acquiring read lock reentrantly
                if (firstReader == current) {
                    // assert firstReaderHoldCount > 0;
                } else {
                    if (rh == null) {
                        rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current)) {
                            rh = readHolds.get();
                            if (rh.count == 0)
                                readHolds.remove();
                        }
                    }
                    if (rh.count == 0)
                        return -1;
                }
            }
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            //这里 SHARED_UNIT 就是1<<<16
            if (compareAndSetState(c, c + SHARED_UNIT)) {
                if (sharedCount(c) == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    if (rh == null)
                        rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                    cachedHoldCounter = rh; // cache for release
                }
                return 1;
            }
        }
    }

代码精简化如下:

总得来说和普通的 共享锁差不多,主要就是state状态设置为了 sate + (1<<< 16)

protected final int tryAcquireShared(int unused) {
    for (;;) {
        int c = getState();
        int nextc = c + (1 << 16);
        if (nextc < c)
            throw new Error("Maximum lock count exceeded");
        if (exclusiveCount(c) != 0 && owner != Thread.currentThread())
            return -1;
        if (compareAndSetState(c, nextc))
            return 1;
    }
}

读锁的释放也是同共享锁的释放一样 也不再赘述!

ReentranReadWriteLock锁的降级

锁的降级是指 在获取到写锁后降级为读锁。

具体来说就是 获取到写锁后,又获取读锁,再释放写锁,最后释放读锁。

那么为什么不先释放写锁再获取读锁呢?

主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。

如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。

锁降级的原理:

在读锁获取时 都会判断当前执行的线程是不是拥有锁的线程,如果是就会加读锁成功

代码示例:

public void processData() {
    readLock.lock();
    if (!update) {
    // 必须先释放读锁
        readLock.unlock();
    // 锁降级从写锁获取到开始
        writeLock.lock();
        try {
            if (!update) {
            // 准备数据的流程(略)
                update = true;
            }r
            eadLock.lock();
        } finally {
            writeLock.unlock();
        }
        // 锁降级完成,写锁降级为读锁
    }try {
        // 使用数据的流程(略)
    } finally {
        readLock.unlock();
    }
}

本文主要内容来自《java并发编程艺术》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值