写点什么

AQS 之 ReentrantReadWriteLock 写锁

用户头像
伯阳
关注
发布于: 2021 年 01 月 22 日
AQS之ReentrantReadWriteLock写锁

1. 用法

1.1 定义一个安全的 list 集合


public class LockDemo  {ArrayList<Integer> arrayList = new ArrayList<>();//定义一个集合// 定义读锁ReentrantReadWriteLock.ReadLock readLock = new    ReentrantReadWriteLock(true).readLock();// 定义写锁ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock(true).writeLock();
public void addEle(Integer ele) {writeLock.lock(); // 获取写锁arrayList.add(ele);writeLock.unlock(); // 释放写锁}public Integer getEle(Integer index) {try{readLock.lock(); // 获取读锁Integer res = arrayList.get(index);return res;} finally{readLock.unlock();// 释放读锁}}}
复制代码

1.2 Sync 源码中的属性与方法在上一篇文章中已经讲过了


2. 获取写锁源码分析


ReentrantReadWriteLock 中的 lock 方法


public void lock() {sync.acquire(1);}
复制代码

AbstractQueuedSynchronizer 中的 acquire 方法

public final void acquire(int arg) {    // 获取锁失败则进入阻塞队列if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
复制代码

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))*,中的 acquireQueued 方法和 addWaiter 方法在前面的文章中都已经进行了详细的解释说明。*


ReentrantReadWriteLock 中的 tryAcquire 方法


protected final boolean tryAcquire(int acquires) {    // 获取当前线程    Thread current = Thread.currentThread();    //  获取状态    int c = getState();    // 计算写线程数量就是独占锁的可从入数量    int w = exclusiveCount(c);    // 当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁    if (c != 0) {       // 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;       // 如果写锁状态不为0且写锁没有被当前线程持有返回false        if (w == 0 || current != getExclusiveOwnerThread())            return false;       // 判断同一线程获取写锁是否超过最大次数(65535),支持可重入             if (w + exclusiveCount(acquires) > MAX_COUNT)            throw new Error("Maximum lock count exceeded");         //更新状态         //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可        setState(c + acquires);        return true;    }      //到这里说明此时c=0,读锁和写锁都没有被获取      //writerShouldBlock表示是否阻塞    if (writerShouldBlock() ||        !compareAndSetState(c, c + acquires))        return false;        // 设置锁为当前线程所有    setExclusiveOwnerThread(current);    return true;}static final class FairSync extends Sync {// 写锁是否应该被阻塞    final boolean writerShouldBlock() {return hasQueuedPredecessors();}}
复制代码

3. 获取写锁流程图

3.1 流程图获取写锁过程



3.2 流程图获取写锁过程解析


写锁的获取过程如下:


  1. 首先获取 c、w。c 表示当前锁状态;w 表示写线程数量。然后判断同步状态 state 是否为 0。如果 state!=0,说明已经有其他线程获取了读锁或写锁。

  2. 如果锁状态不为零(c != 0),而写锁的状态为 0(w = 0),说明读锁此时被其他线程占用,所以当前线程不能获取写锁,自然返回 false。或者锁状态不为零,而写锁的状态也不为 0,但是获取写锁的线程不是当前线程,则当前线程也不能获取写锁。

  3. 判断当前线程获取写锁是否超过最大次数,若超过,抛异常,反之更新同步状态(此时当前线程已获取写锁,更新是线程安全的),返回 true。

  4. 如果 state 为 0,此时读锁或写锁都没有被获取,判断是否需要阻塞(公平和非公平方式实现不同),在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),如果不需要阻塞,则 CAS 更新同步状态,若 CAS 成功则返回 true,失败则说明锁被别的线程抢去了,返回 false。如果需要阻塞则也返回 false。

  5. 成功获取写锁后,将当前线程设置为占有写锁的线程,返回 true。

  6. 获取锁失败的话,将当前线程进行放入阻塞队列中。

4. 释放写锁源码分析


ReentrantReadWriteLock 中的 unlock 方法


public void unlock() {    sync.release(1);}
复制代码

AbstractQueuedSynchronizer 中的 release 方法

public final boolean release(int arg) {    // 如果返回true 那么释放成功了    if (tryRelease(arg)) {        Node h = head;        // 如果头部不为空,并且头节点的waitStatus是唤醒状态那么唤醒后继线程        if (h != null && h.waitStatus != 0)         // 唤醒后继线程            unparkSuccessor(h);        return true;    }    return false;}
复制代码

ReentrantReadWriteLock 中 tryRelease 方法

protected final boolean tryRelease(int releases) {// 若锁的持有者不是当前线程,抛出异常    if (!isHeldExclusively())     // 非法的监控器异常        throw new IllegalMonitorStateException();        // 计算写锁的新线程数    int nextc = getState() - releases;    // 如果独占模式重入数为0了,说明独占模式被释放    boolean free = exclusiveCount(nextc) == 0;    if (free)     // 设置独占线程为空        setExclusiveOwnerThread(null);    // 设置写锁的新线程数    // 不管独占模式是否被释放,更新独占重入数    setState(nextc);    return free;}protected final boolean isHeldExclusively() {    // 若当前线程是当前锁的持有线程那么返回true    return getExclusiveOwnerThread() == Thread.currentThread();}
复制代码

5. 释放写锁流程图

5.1 流程图释放过程


5.2 流程图释放过程解析


写锁的释放过程:


  1. 首先查看当前线程是否为写锁的持有者,如果不是抛出异常。然后检查释放后写锁的线程数是否为 0,如果为 0 则表示写锁空闲了,释放锁资源将锁的持有线程设置为 null,否则释放仅仅只是一次重入锁而已,并不能将写锁的线程清空。

  2. 说明:此方法用于释放写锁资源,首先会判断该线程是否为独占线程,若不为独占线程,则抛出异常,否则,计算释放资源后的写锁的数量,若为 0,表示成功释放,资源不将被占用,否则,表示资源还被占用。

6. 总结

6.1 state 解析


private volatile int state;
复制代码

int 类型占有 4 个字节一个字节 8 位,所以 state 一个 32 位,高 16 位 代表读锁 低 16 位代表 写锁。

// 0x0000FFFF 16 进制// 1111111111111111 2 进制// 65535 10 进制static final int SHARED_SHIFT   = 16;static final int SHARED_UNIT    = (1 << SHARED_SHIFT); // 65536static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; //65535  // 1111111111111111static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535 // 1111111111111111
复制代码

如果此时同步状态位 c 那么获取写状态 c & EXCLUSIVE_MASK

如果此时同步状态位 c 那么获取读状态 c >>>16 无符号补 0,右移 16 位


6.2 注意


*以上便是 ReentrantReadWriteLock 中写锁的分析,下一篇文章将是**Condition**的分析,如有错误之处,帮忙指出及时更正,谢谢,*如果喜欢谢谢点赞加收藏加转发(转发注明出处谢谢!!!)


发布于: 2021 年 01 月 22 日阅读数: 11
用户头像

伯阳

关注

所有牛逼的人都有一段苦逼的岁月!只有坚持 2019.07.03 加入

一个懂得生活的程序员,世界是多维度的,我们要看的开心、玩的开心、活的开心

评论

发布
暂无评论
AQS之ReentrantReadWriteLock写锁