小白系列--ReentrantReadWriteLock源码学习

ReentrantReadWriteLock

简介

在并发编程中,为了确保数据的安全性,我们常常采用synchronized来对代码段进行加锁,防止其他线程同时对数据进行修改,从而导致数据不一致的问题,大多数人都知道synchronized是一个重量级的锁(JDK 6 之前),在线程切换时OS会发生用户态到内核态切换,因此使用他来加锁,在线程数量较多的情况下会严重影响程序的性能,因此后面JDK引入了Java层面加锁的机制,即Java.util.concurrent包下的类

ReentrantReadWriteLock的引入主要是为了控制读操作可以让所有线程同时进行,在写操作进行时,所有的读线程和其他写线程均被阻塞,在大多数情况下,读都是大于写的,因此读写锁能够提供比排它锁更好的并发性和吞吐量

由前面的学习我们知道AQS中是由state来维持当前资源数量,在ReentranReadWriteLock中同样是使用这个state变量来维护读写线程的状态信息, 因为state是int,在64位机器中int是有32位字节来存储,为了区分读写的状态,将高16位来记录读锁的信息,低16位记录写锁的状态信息,如:

知道了区分的方式,如何计算读状态,写状态呢,相信你已经知道了答案,位运算

// 在ReentrantReadWriteLock中有几个属性
// SHARED_SHIFT表示计算读时应该移动的位数
static final int SHARED_SHIFT   = 16;
// 用于计算读锁数量
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
// 读写资源的最大数量, 即16位都为1, 等于2^16 - 1
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
// 计算写锁数量, 低16位全为1,其余都为0
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

// 计算c对应的读锁数量, 即将高16位右移16位
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
// 计算c对应的写锁数量, 即c跟低16位都为1的EXCLUSIVE_MASK 作与运算
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

先看下ReentrantReadWriteLock的继承关系吧:

下面开始分析源码:

基本属性:

// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
// 继承了同步器AQS
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);
}

// 内部重写了try开头的一些方法。。。
abstract static class Sync extends AbstractQueuedSynchronizer {
}

// 公平锁的实现
static final class FairSync extends Sync {
    final boolean writerShouldBlock() {
        // 当前节点是否需要排队
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}
// 非公平锁的实现
static final class NonfairSync extends Sync {
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
    	// 返回同步队列头结点的后继节点是否是一个写线程的节点
        return apparentlyFirstQueuedIsExclusive();
    }
}

写锁实现:

  • 写锁是采用的独占模式,当线程持有写锁时,其他读、写将会阻塞
acquire :

跟ReentrantLock一样

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
tryAcquire
protected final boolean tryAcquire(int acquires) {
    /*
             * Walkthrough:
             * 1. 如果读锁非0, 写锁非0, 有其他线程持有独占锁,返回-1.
             * 2. 如果当前数量已经饱和
             */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    // 锁资源是否为0
    if (c != 0) {
        // condition1 成立: 没有写锁,说明有读锁存在,那么这个写锁将进入同步队列等待,当读锁释放完成后,在执行写操作
        // condition2 成立: 说明有其他线程已经持有写锁,当前写锁应该进入同步队列等待
        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;
    }
    // 目前还没有线程获取过锁
    // condition1: 非公平下writerShouldBlock直接返回false
    // 				公平模式下writerShouldBlock返回当前线程是否需要入队等待
    // condition2: CAS修改state值,失败则表示获取锁失败 
    
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 写锁获取成功,设置当前线程为独占线程
    setExclusiveOwnerThread(current);
    return true;
}

addWaiter、acquireQueued
  • AQS内部方法,没什么说的,就是入队的操作,之前介绍过
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
release

释放写锁

public final boolean release(int arg) {
    // 条件成立: 当前线程已经释放完了写锁
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 唤醒同步队列的后续节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}
tryRelease
protected final boolean tryRelease(int releases) {
    // 当前线程是否获取到写锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 计算释放写锁资源后剩余资源数
    int nextc = getState() - releases;
    // 释放锁资源后,是否还有写锁存在,可能当前线程重入了多个写锁
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        // 没有写锁存在,那么设置写锁线程为null
        setExclusiveOwnerThread(null);
    // 设置写锁资源数
    setState(nextc);
    return free;
}
执行流程图:

读锁实现

由于读取数据,并不会影响数据,因此多个线程可以同时读,故使用共享模式实现读操作

acquireShared
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
tryAcquireShared
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    // 成立: 说明写锁被其他线程持有,返回-1
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    // condition1:  readerShouldBlock(), 是否需要阻塞,
    // 				如果队列第一个线程是写锁那么就阻塞
    // condition2: 是否大于最大资源
    // condition3: CAS 将读状态+1
    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;
            // condition1: 表示cachedHoldCounter还没有被赋值
            // condition2: 表示上一个读线程tid跟当前线程id不同,就表示不同线程吧
            if (rh == null || rh.tid != getThreadId(current))
                // 对cachedHoldCounter重新赋值为最新读线程所持有的资源
                cachedHoldCounter = rh = readHolds.get();
            
            // rh所记录线程 跟当前线程相同, 为啥这里会为0
            else if (rh.count == 0)
                // 重新set,貌似没用, readHolds.get()执行后,已经有threadLocal对应的值存在吧
                readHolds.set(rh);
            // 记录最后一个读线程读的次数
            rh.count++;
        }
        return 1;
    }
    // 队列头结点是写操作线程, 或则修改读状态失败,进行重试
    return fullTryAcquireShared(current);
}

fullTryAcquireShared
// 同步队列第一个写操作节点正在阻塞,或CAS修改读状态失败走到这里
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        // 是否有线程持有写锁
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)   
                return -1;    // 写锁不是当前线程,当前读线程将入队(doAcquireShared)
            // 写锁就是当前线程,继续向下执行
        } else if (readerShouldBlock()) {   // 同步队列中第一个节点是写锁,正在等待获取写锁
            // 确保我们不会再次修改firstReader
            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");

        // 当前线程持有写锁,再次获取读锁, T1(写) -> T1(读) -> T1(读)
        // 或则3-7行之间T1写锁被释放,其他线程来请求读锁, T1 发生锁降级
        // T1(写) -> T1(读) -> T1(读) -> T2(读)
        if (compareAndSetState(c, c + SHARED_UNIT)) {   // 增加读锁
            if (sharedCount(c) == 0) {  // 之前没有读锁,那么修改firstReader为当前线程
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {  // 之前已经有读锁,重入,增加读锁的数量
                firstReaderHoldCount++;
            } else {  // T1写锁被释放,即T1锁降低, T2来请求读锁
                if (rh == null)
                    rh = cachedHoldCounter;
                // 如果是T1写锁被释放,T2来请求读锁 那么这里condition2成立,重新设置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;
        }
    }
}
doAcquireShared

当tryAcquireShared失败后,走到这里

获取共享锁,不响应中断,自己处理中断信息,响应中断就是发现中断直接抛异常

private void doAcquireShared(int arg) {
    // 新增一个节点到同步队列中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // node前驱
            final Node p = node.predecessor();
            // 前驱是否为head, 如果是则重新获取锁
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 成立: 重新获取锁成功
                if (r >= 0) {
                    // 重新将node设置为head,唤醒head后的读线程
                    setHeadAndPropagate(node, r);
                    // p 已经成功获得资源,可以从同步队列中移除
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // shouldParkAfterFailedAcquire(): 将p的status设置为signal
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
setHeadAndPropagate
// 设置node为head,如果head.next 为共享模式,那么唤醒head.next元素
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);

    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
unlock

释放锁资源

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

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 唤醒队列中head的后续节点
        doReleaseShared();
        return true;
    }
    return false;
}
tryReleaseShared
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 第一个读锁线程跟当前线程相同
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        // 只有一个线程持有读锁,直接清空
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {	// 释放的读锁线程不是firstReader
        HoldCounter rh = cachedHoldCounter;
		// cachedHoldCounter存储的线程不是当前线程,那么更新cachedHoldCounter为当前线程的ThreadLocal信息
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            // 移除ThreadLocal元素
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        // CAS 更新读锁的资源数量
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}
doReleaseShared
  • tryReleaseShared()方法执行后如果返回true,则走这个方法,说明此时没有读锁线程存在了,唤醒同步队列中的其他等待线程

private void doReleaseShared() {

    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 如果head状态为SIGNAL,说明可以唤醒同步队列后面的线程
            if (ws == Node.SIGNAL) {
                // 修改SIGNAL为0, 将其从同步队列中去除
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒head后续节点
                unparkSuccessor(h);
            }
            // 如果ws为0,那么更新状态为PROPAGATE
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
执行流程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值