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方法java

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方法在前面的文章中都已经进行了详细的解释说明。**
在这里插入图片描述ide

       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 流程图获取写锁过程解析

写锁的获取过程以下:ui

  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方法3d

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方法code

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 流程图释放过程解析

写锁的释放过程:blog

  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); // 65536
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; //65535  
// 1111111111111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535 
// 1111111111111111

       若是此时同步状态位 c 那么获取写状态 c & EXCLUSIVE_MASK
       若是此时同步状态位 c 那么获取读状态 c >>>16 无符号补0,右移16位

6.2 注意

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

相关文章
相关标签/搜索