前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >读写锁ReentrantReadWriteLock 的实现原理

读写锁ReentrantReadWriteLock 的实现原理

作者头像
AI码师
发布2022-09-19 11:49:50
2770
发布2022-09-19 11:49:50
举报

今天给大家介绍下读写锁,在引入读写锁之前,给大家说一个案例:

在很多场景下,我们用到的都是互斥锁,线程间相互竞争资源;但是有时候我们的场景会存在读多写少的情况,这个时候如果还是使用互斥锁,就会导致资源的浪费,为什么呢?

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

ReentrantReadWriteLock锁的使用

提供的方法

readLock()

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

writeLock()

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

getReadLockCount()

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

getReadHoldCount()

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

isWriteLocked()

判断写锁是否被获取

getWriteHoldCount()

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

代码演示

代码语言:javascript
复制
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();
        }
    }
}

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

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

读写锁的实现原理

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

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

读写状态的设计

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

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

写锁的获取与释放

获取锁的源码

代码语言:javascript
复制
 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;
        }

释放锁的源码

代码语言:javascript
复制
        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;
        }

读锁的获取与释放

获取锁的源码

代码语言:javascript
复制
 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);
        }

释放锁的源码

代码语言:javascript
复制
  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;
            }
        }

锁降级

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

关注公众号领取2022最新面试题一套和项目源码

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 乐哥聊编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ReentrantReadWriteLock锁的使用
    • 提供的方法
      • readLock()
      • writeLock()
      • getReadLockCount()
      • getReadHoldCount()
      • isWriteLocked()
      • getWriteHoldCount()
    • 代码演示
    • 读写锁的实现原理
      • 读写状态的设计
        • 写锁的获取与释放
          • 读锁的获取与释放
          • 锁降级
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档