9.一文搞懂RenntrantReadWriteLock 读写锁的实现原理

今天给大家介绍下读写锁,在引入读写锁之前,给大家说一个案例:
在很多场景下,我们用到的都是互斥锁,线程间相互竞争资源;但是有时候我们的场景会存在读多写少的情况,这个时候如果还是使用互斥锁,就会导致资源的浪费,为什么呢?

因为如果同时都在读的时候,是不需要锁资源的,只有读和写在同时工作的时候才需要锁资源,所以如果直接用互斥锁,肯定会导致资源的浪费。

RenntrantReadWriteLock锁的使用

提供的方法

readLock()

获取读锁对象(不是获取锁资源)

writeLock()

获取写锁对象(不是获取锁资源)

getReadLockCount()

获取当前读锁被获取的次数,包括线程重入的次数

getReadHoldCount()

返回当前线程获取读锁的次数

isWriteLocked()

判断写锁是否被获取

getWriteHoldCount()

返回当前写锁被获取的次数

代码演示

package com.ams.thread.lesson8;

import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 关注微信公众号"AI码师"获取项目源码及2021面试题一套
 *
 * @author: AI码师
 * Date: 2021/12/30 6:08 上午
 * Description:
 */
@Slf4j
public class Example20 {
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static Lock readLock = lock.readLock();
    private static Lock writeLock = lock.writeLock();

    public static void main(String[] args) {
        new Thread(new Thread1("读线程1")).start();
        new Thread(new Thread1("读线程2")).start();
        new Thread(new Thread1("读线程3")).start();
        new Thread(new Thread1("读线程4")).start();
        new Thread(new Thread2("写线程1")).start();
        new Thread(new Thread2("写线程2")).start();
    }


    /**
     * 读锁线程
     */
    static class Thread1 implements Runnable {
        private String name;
        public Thread1(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            readLock.lock();
            int i=0;
            while (i++<10){
                log.info("线程 {} 在运行",name);
                ThreadUtil.sleep(1000);
            }
            readLock.unlock();
        }
    }
    /**
     * 写锁线程
     */
    static class Thread2 implements Runnable {
        private String name;
        public Thread2(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            writeLock.lock();
            int i=0;
            while (i++<5){
                log.info("线程 {} 在运行",name);
                ThreadUtil.sleep(1000);
            }
            writeLock.unlock();
        }
    }
}

9.一文搞懂RenntrantReadWriteLock 读写锁的实现原理_第1张图片

从结果中可以看出来有三种结果,所以分析看来

  • 读线程是交叉执行的,并且此时没有写线程在执行
  • 写线程是独立执行,不和读线程交叉执行,并且,两个写线程也没有交叉执行。

读写锁的实现原理

前面我们说过,并发包中的锁都是基于AQS实现的,锁的状态都是基于state变量进行维护的,那么在读写锁中,state是如何维护这两个状态呢?

我们想必在读其它框架源码时候,应该也会看到过类似的问题,既然想要一个变量表示多个状态,那么我们就得把这个变量进行拆分,就是拆成bit位进行表示,没错:读写锁的状态位就是这么设计的!

读写状态的设计

在这里插入图片描述

我们分析下读写状态位的设计:

  • 高16位:代表读状态,计算时通过右移16位,就可以算出当前读锁被获取多少次
  • 低16位:代表写状态,直接计算得出写锁被获取多少次

写锁的获取与释放

获取锁的源码

 protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                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;
        }

释放锁的源码

        protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

读锁的获取与释放

获取锁的源码

 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);
        }

释放锁的源码

  protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                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;
                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;
            }
        }

锁降级

当前线程先获取到写锁,然后再获取读锁,再把写锁释放,最后释放读锁

你可能感兴趣的:(#,并发编程,java,面试,开发语言)