Java并发编程--锁原理之读写锁ReentrantReadWriteLock

读写锁ReentrantReadWriteLock的原理

​  实际应用中,写少读多的情况很多,而使用ReentranLock性能太低(只有读锁时不需要限制其他读锁).ReentrantReadWriteLock应运而生,采用读写分离的策略,允许多个线程可以同时获取读锁.

(1). 结构

在这里插入图片描述
​  内部维护了一个ReadLock和一个WriteLock,它们都是依赖Sync实现具体功能,而Sunc继承自AQS.AQS中的state只维护了一个状态,而读写锁有两个锁,所以采用state的高16为表示读锁的状态,低16位表示写锁的状态

(2). 写锁的获取与释放

​  写锁使用WriteLock实现.

1). void lock()

​  写锁是个可重入的独占锁,当没有线程获取读锁且写锁空闲或者写锁属于当前线程时,可以获取

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

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 此方法在WriteLock(内部类)中实现
protected final boolean tryAcquire(int acquires) {
    // 获取当前线程,写锁的重入次数W
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    // 写锁或者读锁已经被获取
    if (c != 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;
    }
    // 第一个写线程获取写锁
    // writerShouldBlock()当有别的线程也在获取此锁时,是否应该阻塞(分公平和非公平两种实现)
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

// 非公平锁,不需要判断,不阻塞
final boolean writerShouldBlock() {
    return false; // writers can always barge
}

//公平锁,调用hasQueuedPredecessors()判断是否有前继节点,如果有说明不是队首,继续排队,放弃获取锁
final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
}

2). void lockInterruptibly()

​  同样是获取锁,此方法会对中断进行响应.调用的是AQS中响应中断的获取锁方法.

3). boolean tryLock()

​  尝试获取写锁,没有获取到不会阻塞,逻辑同上.

4). boolean tryLock(long timeout, TimeUnit unit)

​  获取锁失败后挂起指定时间,如果时间到了之后还是没有获取到锁,返回false

5). void unlock()

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 上面都是AQS的内容,下面的方法是具体的锁实现的
protected final boolean tryRelease(int releases) {
    //判断是否是写锁的拥有者调用的unlock
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 是当前线程获取的写锁
    // 获取可重入值,写锁是低16位,可以直接加减.如果写锁重入次数为0,则锁应修改为空闲状态
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

(3). 读锁的获取与释放

​  读锁是使用ReadLock实现的.

1). void lock()

​  获取读锁,如果写锁是空闲的,其他线程都可以获取读锁.读锁是共享的

public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

// 具体的锁的实现
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 + SHARxianchengED_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;
    }
    // 没有获取成功的线程进入fullTryAcquireShared,逻辑与tryAcquireShared()类似,但是是自旋的
    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 if (readerShouldBlock()) {
            // 此线程是第一个得到读锁的线程
            if (firstReader == current) {
                // doNothing
            } 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");
        // 获取锁
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // 当前锁空闲
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            }
            // 如果firstReader是当前线程,或者当前线程的cachedHoldCounter变量的count不为0(表示当前线程已经持有了该共享锁),均说明当前线程已经持有共享锁,此次获取共享锁是重入,这也是允许的,可以通过判断。
            // 此处将重入次数分为fistReader的重入次数和其他所有线程的重入次数之和
            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;
        }
    }
}

2). void lockInterruptibly()

​  可以对中断进行响应的获取锁.

3). boolean tryLock()

​  尝试获取锁,没有成功不会阻塞.

4). boolean tryLock(long timeout, TimeUnit unit)

​  如果超时时间内(挂起了)还没有获取锁,返回false.

5). void unlock()

public void unlock() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

// 具体锁实现的逻辑
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 对第一个获取锁的线程的重入次数进行更新
    if (firstReader == current) {
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    }
    // 其他线程重入次数的更新
    else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    // 自旋的释放锁
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        // CAS成功则说明当前线程没有被其他线程打扰
        if (compareAndSetState(c, nextc))
            // 返回值为读锁是否空闲
            return nextc == 0;
    }
}

最后用一幅图加深理解

在这里插入图片描述

发布了141 篇原创文章 · 获赞 47 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41596568/article/details/104076008