• 从ReentrantReadWriteLock开始的独占锁与共享锁的源码分析


    FBI WARNING(bushi)

    当涉及sync调用时,并不会分析尝试获取和释放之后的后继逻辑,因为这个逻辑是由AQS类实现的。请看姊妹篇之并发入门组件AQS源码解析。

    开始的开始是一个demo

    以下的代码,会将独占锁持有5分钟,在此期会阻塞住后面两个线程的共享锁请求,独占锁释放后,两个线程会同时拥有共享锁,即使没有进行unlock()

    public class lockTest {
    
        public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
        static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    
        public static void main(String[] args) {
            new Thread(()->{
                writeLock.lock();
                try {
                    Thread.sleep(300000);
                    System.out.println("写锁释放");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                writeLock.unlock();
            }).start();
            new Thread(()->{
                readLock.lock();
                try {
                    System.out.println("读锁1");
                    Thread.sleep(300000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                readLock.unlock();
            }).start();
            new Thread(()->{
                readLock.lock();
                try {
                    System.out.println("读锁1");
                    Thread.sleep(300000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                readLock.unlock();
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    ReentranReadWriteLock,writeLock,readLock,AQS的关系

    让我们先进入第一行,
    public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    可以看到,默认创建了一个非公平的AQS,然后将这个AQS分别给了读锁和写锁。
    请添加图片描述
    请添加图片描述
    而后面两行

    static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    就只是返回已经建立好的对象
    请添加图片描述

    独占锁获取

    进入

    我们进入上面的代码writeLock.lock();
    在这里插入图片描述
    进入了老熟客acquire方法。
    在这里插入图片描述

    核心方法

    总体大概思路是,判断是否锁是否有被使用,如果有被使用而是自己,那重入,不然就失败,如果没被使用就设定为自己。

    protected final boolean tryAcquire(int acquires) {
                Thread current = Thread.currentThread();
                //获取状态并且检查独占进入次数
                int c = getState();
                int w = exclusiveCount(c);
                if (c != 0) {
                    // 独占次数为0,但是状态却不为0,那么说明现在存在着共享锁。如果独占锁不为自己,状态却不为0,那么说明现在存在着独占锁,这两种情况都无法获取到锁
                    if (w == 0 || current != getExclusiveOwnerThread())
                        return false;
                    // 达到MAX_COUNT说明重入了65536次,九成九九九都是代码写错了!
                    if (w + exclusiveCount(acquires) > MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                    // 状态加上当前重入次数
                    setState(c + acquires);
                    return true;
                }
                //前一个条件是公平锁和非公平锁的关键差异。后一个,如果c发生了变化,说明有线程同样参与了该AQS状态的变化并且早于自己完成了修改,那么默认自己失败,放弃。
                if (writerShouldBlock() ||
                    !compareAndSetState(c, c + acquires))
                    return false;
                //将独占锁所有者设定为自己,并返回成功获取
                setExclusiveOwnerThread(current);
                return true;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    请添加图片描述

    state

    state是AQS里面一个32位长的整型。
    private volatile int state;
    后16位说明独占锁状态,数值表示这个独占锁被持有者重入了多少次。
    前16位说明共享锁状态,数值表示这个共享锁被持有了多少次。(请注意,一个线程可以多次持有读锁)

    公平与非公平的界定

    我们可以清晰看到,公平和非公平只在于writerShouldBlock()和readerShouldBlock()的重写
    请添加图片描述
    先看看非公平锁,非公平锁并不会对独占进行操作
    请添加图片描述
    再看看公平锁,写锁会进行判断。如果队列不为空,并且队列里争抢头节点的线程不为自己,说明有线程在排队,于是tryAcquired失败,
    请添加图片描述

    请添加图片描述

    独占锁释放

    请添加图片描述
    实际上release返回false并不重要,我们可以看出上层并没有对其返回值进行逻辑操作。顺带一提,unparkSuccessor也在AQS篇。
    请添加图片描述

    protected final boolean tryRelease(int releases) {
    	// 进行锁持有检验与释放
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
         int nextc = getState() - releases;
         // 如果释放了之后不为0,说明重入了,那么在release层就直接返回false而不执行后续的唤醒后继者。
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    共享锁获取

    进入

    回到最初的最初,让我们看一下共享锁的获取流程
    请添加图片描述
    调用的是ReadLock下面的lock(),可以看出,也是对sync进行调用操作,只不过独占锁调用的是acquire,而共享锁调用的是acquireShared
    请添加图片描述
    try尝试获取一下
    请添加图片描述

    核心方法

    在以下代码中,我们常常会看到读锁对HoldCounter进行了修改,其实这是为了返回当前线程重入了多少次,但是对初学者而言,这并不关乎锁的理解,反而会引起更多不必要的逻辑理解,可以直接忽略

    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);
        //如果成功获取,那么必定返回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,就去ThreadLocalMap取出线程私有的HoldCounter,存入缓存
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                // 如果自己缓存数量为0,就放入count为1的HoldCounter
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return 1;
        }
        //如果不是因为存在独占锁,而是因为共享锁共同compareAndSetState导致失败,或者队列中首线程正在争抢独占锁(还没抢到),那么就会进入完全尝试
        return fullTryAcquireShared(current);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    请添加图片描述
    请添加图片描述
    如果头节点不为空,并且头节点的下一个不为空,并且头节点的下一个不是共享的,并且该节点没有被取消,那么就判定正在争抢锁的线程是抢独占锁的。如果对方抢独占锁,那就直接放行,这是为了避免前面判断完了非独占,但是到了这一行,其他线程又已经上了独占锁这种尴尬现象。
    请添加图片描述

    进入完全尝试

    完全尝试分为三部分,一,有独占锁且不是自己,直接失败。二,自己没有获取过读锁只是首次尝试,直接返回false,不再尝试。三,前两者都通过的情况下,尝试成功获取锁。

            final int fullTryAcquireShared(Thread current) {
                HoldCounter rh = null;
                for (;;) {
                    int c = getState();
                    //如果存在独占锁,但不是自己,直接失败,
                    if (exclusiveCount(c) != 0) {
                        if (getExclusiveOwnerThread() != current)
                            return -1;
    				//readerShouldBlock上面有解析,说明有线程在抢独占锁
                    } else if (readerShouldBlock()) {
                        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");
                    if (compareAndSetState(c, c + SHARED_UNIT)) {
                        if (sharedCount(c) == 0) {
                            firstReader = current;
                            firstReaderHoldCount = 1;
                        } 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;
                    }
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    释放共享锁

    进入

    执行readLock.unlock();
    在这里插入图片描述
    在这里插入图片描述

    核心方法

    protected final boolean tryReleaseShared(int unused) {
                Thread current = Thread.currentThread();
                // 如果是第一个获取共享锁的,直接将firstReader改为null
                if (firstReader == current) {
                    // assert firstReaderHoldCount > 0;
                    if (firstReaderHoldCount == 1)
                        firstReader = null;
                    else
                        firstReaderHoldCount--;
                } else {
                	// 否则取出当前线程的重入次数,并减一,如果只剩一次了,那么就从map中删除当前线程的计数器
                    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;
                }
                //一直尝试将状态值减1直到成功
                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;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    利用MobaXterm连接服务器的全程配置
    Docker部署Nacos2.0单机版+mysql8
    数据艺术:精通数据可视化的关键步骤
    Spring原理篇
    【Acorn】JS解析器编译原理
    关于安卓SVGA浅尝(一)svgaplayer库的使用
    十.EtherCAT开发之microchip MCU D51+ LAN9253 的开发COE应用(SPI directly 模式)
    volatile关键字 和 i = i + 1过程
    LeetCode 0592. 分数加减运算:手把手分步のC++讲解
    【项目】云备份系统基础功能实现
  • 原文地址:https://blog.csdn.net/Liyizhi111/article/details/127916098