ReentrantReadWriteLock源码讲解

类的继承关系

java 复制代码
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
}

实现了Serializable接口,表示可以进行序列化.还实现了ReadWriteLock接口.源码如下.

csharp 复制代码
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

ReadWriteLock接口接口定义了获取读锁和写锁的规范,具体需要实现类去实现.

内部类

ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的。

Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类;ReadLock实现了Lock接口、WriteLock也实现了Lock接口。

Sync类

scala 复制代码
abstract static class Sync extends AbstractQueuedSynchronizer {}

Sync抽象类继承自AQS抽象类.Sync类又是读写锁的成员变量.

类的内部类

Sync类内部存在两个内部类,分别为HoldCounter和ThreadLocalHoldCounter,其中HoldCounter主要与读锁配套使用,其中,HoldCounter源码如下。

arduino 复制代码
        /**
         * A counter for per-thread read hold counts.
         * Maintained as a ThreadLocal; cached in cachedHoldCounter
         */
        static final class HoldCounter {
            int count = 0;
            //返回给定线程的线程id
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }

HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程指定线程id.用来唯一标识一个线程.

ThreadLocalHoldCounter的源码如下.

csharp 复制代码
static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            // 重写threadLcoal初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,ThreadLocal类可以将线程与对象相关联。在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象.

ThreadLcoal的setInitialValue方法.如果创建过了,直接设置值,没有就创建一个.

scss 复制代码
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

类的属性

java 复制代码
abstract static class Sync extends AbstractQueuedSynchronizer {
    // 版本序列号
    private static final long serialVersionUID = 6317671515068378041L;        
    // 高16位为读锁,低16位为写锁
    static final int SHARED_SHIFT   = 16;
    // 读锁单位
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 读锁最大数量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 写锁最大数量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 本地线程计数器
    private transient ThreadLocalHoldCounter readHolds;
    // 缓存的计数器
    private transient HoldCounter cachedHoldCounter;
    // 第一个读线程
    private transient Thread firstReader = null;
    // 第一个读线程的计数
    private transient int firstReaderHoldCount;
}

构造函数

scss 复制代码
 Sync() {
            //本地计数器
            readHolds = new ThreadLocalHoldCounter();
            //设置状态数.
            setState(getState()); // ensures visibility of readHolds
        }

核心函数

sharedCount函数表示占有读锁的线程数量,源码如下

arduino 复制代码
        /** Returns the number of shared holds represented in count  */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

直接将state右移16位,就可以得到读锁的线程数量,因为state的高16位表示读锁,对应的低十六位表示写锁数量。

exclusiveCount函数表示占有写锁的线程数量,源码如下

arduino 复制代码
        /** Returns the number of exclusive holds represented in count  */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

直接将状态state和(2^16 - 1)做与运算,其等效于将state模上2^16。写锁数量由state的低十六位表示。

tryRelease函数

scss 复制代码
/*
         * Note that tryRelease and tryAcquire can be called by
         * Conditions. So it is possible that their arguments contain
         * both read and write holds that are all released during a
         * condition wait and re-established in tryAcquire.
         */

        protected final boolean tryRelease(int releases) {
            //判断是不是独占线程.
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //状态值减一.
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;//判断写锁是否为0.
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

用于释放写锁释放.先判断当前线程是否我独占线程,不是则抛出异常.状态值减一,然后判断写锁是否释放,释放的话则将独占线程置为空,否则状态值减一.

tryAcquire函数

scss 复制代码
        protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            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;
        }

此函数用于获取写锁,首先会获取state,判断是否为0,若为0,表示此时没有读锁线程,再判断写线程是否应该被阻塞,而在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),之后在设置状态state,然后返回true。若state不为0,则表示此时存在读锁或写锁线程,若写锁线程数量为0或者当前线程为独占锁线程,则返回false,表示不成功,否则,判断写锁线程的重入次数是否大于了最大值,若是,则抛出异常,否则,设置状态state,返回true,表示成功。

非公平

scala 复制代码
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }

公平

scala 复制代码
 static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
java 复制代码
 public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

从上边可以看出公平非公平的区别.

tryReleaseShared函数

ini 复制代码
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;
            }
        }

此函数表示读锁线程释放锁。首先判断当前线程是否为第一个读线程firstReader,若是,则判断第一个读线程占有的资源数firstReaderHoldCount是否为1,若是,则设置第一个读线程firstReader为空,否则,将第一个读线程占有的资源数firstReaderHoldCount减1;若当前线程不是第一个读线程,那么首先会获取缓存计数器(上一个读锁线程对应的计数器 ),若计数器为空或者tid不等于当前线程的tid值,则获取当前线程的计数器,如果计数器的计数count小于等于1,则移除当前线程对应的计数器,如果计数器的计数count小于等于0,则抛出异常,之后再减少计数即可。无论何种情况,都会进入无限循环,该循环可以确保成功设置状态state。

tryAcquireShared函数

scss 复制代码
protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)// 写线程数不为0并且占有资源的不是当前线程
                return -1;
            int r = sharedCount(c);//获取读锁数量.
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {//判断是否要阻塞.是否小于获取读锁的最大数.cas成功.(获取读锁成功)
                if (r == 0) {//如果读锁为零,则把当前线程设置为队首.计数器加一.
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {//如果当前线程等于第一个读锁线程,计数器加一.
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;//如果当前线程的计数器为空或者不等于当前线程.重新获取计数器.如果计数器的count为零,重新设置进readHolds,然后计数器进行加一.
                    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);//如果上面获取锁失败.就会进入这个方法进行不断尝试获取.
        }

fullTryAcquireShared源码如下.

ini 复制代码
  final int fullTryAcquireShared(Thread current) {
            /*
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    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;
                }
            }
        }

此函数表示读锁线程获取读锁。首先判断写锁是否为0并且当前线程不占有独占锁,直接返回;否则,判断读线程是否需要被阻塞并且读锁数量是否小于最大值并且比较设置状态成功,若当前没有读锁,则设置第一个读线程firstReader和firstReaderHoldCount;若当前线程线程为第一个读线程,则增加firstReaderHoldCount;否则,将设置当前线程对应的HoldCounter对象的值.

类的属性

php 复制代码
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
     
    private static final long serialVersionUID = -6992448646407690164L;    
    // 读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    // 写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    // 同步队列
    final Sync sync;
    
    private static final sun.misc.Unsafe UNSAFE;
    // 线程ID的偏移地址
    private static final long TID_OFFSET;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            // 获取线程的tid字段的内存地址
            TID_OFFSET = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("tid"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

构造函数

csharp 复制代码
public ReentrantReadWriteLock() {
    this(false);
}

public ReentrantReadWriteLock(boolean fair) {
    // 公平策略或者是非公平策略
    sync = fair ? new FairSync() : new NonfairSync();
    // 读锁
    readerLock = new ReadLock(this);
    // 写锁
    writerLock = new WriteLock(this);
}

ReentrantReadWriteLock示例

csharp 复制代码
public class ReentrantReadWriteLockTest {

    public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        ReadThread read1 = new ReadThread("read1", lock);
        ReadThread read2 = new ReadThread("read2", lock);
        WriteThread write1 = new WriteThread("write1", lock);
        read1.start();
        read2.start();
        write1.start();
    }
}

class WriteThread extends Thread {
    private ReentrantReadWriteLock lock;

    public WriteThread(String name, ReentrantReadWriteLock lock) {
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "start trying to lock");
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "获取写锁成功");
        } finally {
            lock.writeLock().unlock();
            System.out.println(Thread.currentThread().getName() + "释放写锁成功");
        }
    }
}

class ReadThread extends Thread {
    private ReentrantReadWriteLock lock;

    public ReadThread(String name, ReentrantReadWriteLock lock) {
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " trying to lock");
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " 获取读锁成功");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
            System.out.println(Thread.currentThread().getName() + " 释放读锁成功");
        }
    }
}

可以从结果看出来,读锁是可以多个线程 同时拥有.

read1线程执行rrwLock.readLock().lock操作

read2线程执行rrwLock.readLock().lock操作

wt1线程执行rrwLock.writeLock().lock操作

这个方法和前面的思路差不多,就不做过多的分析,只是给出一个调用流程.

read1线程执行rrwLock.readLock().unlock操作

read2线程执行rrwLock.readLock().unlock操作

read2释放读锁线程后唤醒后续的写线程继续执行.

write线程获得CPU资源,继续运行,需要恢复。由于之前acquireQueued函数中的parkAndCheckInterrupt函数中被禁止的,所以,恢复到parkAndCheckInterrupt函数.

write1执行rrwLock.writeLock().unlock操作

​

语雀地址www.yuque.com/itbosunmian...?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

相关推荐
步行cgn16 分钟前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su32 分钟前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
码事漫谈1 小时前
AI 读你的提示词,其实很没耐心
后端
用户8356290780512 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123452 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒2 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记2 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk598782 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端
雨中飘荡的记忆2 小时前
Claude Code 入门必会:@、!、Slash Commands,一篇讲清楚
人工智能·后端
苜蓿muxu2 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java