当前位置:   article > 正文

读写锁 ReentrantReadWriteLock源码分析

reentrantreadwritelock源码

一、ReentrantReadWriteLock结构

二、读写状态的设计

设计的精髓:用一个变量如何维护多种状态 在 ReentrantLock 中,使用 Sync ( 实际是 AQS )的 int 类型的 state 来表示同步状态,表示锁被一个线程重复获取的次数。但是,读写锁 ReentrantReadWriteLock 内部维护着一对读写锁,如果要用一个变量维护多种状态,需要采用“按位切割使用”的方式来维护这个变量,将其切分为两部分:高16为表示读,低16为表示写。

分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过位运算。假如当前同步状态为S,那么:

  • 写状态,等于 S & 0x0000FFFF(将高 16 位全部抹去)。 当写状态加1,等于S+1.
  • 读状态,等于 S >>> 16 (无符号补 0 右移 16 位)。当读状态加1,等于S+(1<<16),也就是S+0x00010000

根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

代码实现:java.util.concurrent.locks.ReentrantReadWriteLock.Sync

  • exclusiveCount(int c) 静态方法,获得持有写状态的锁的次数。
  • sharedCount(int c) 静态方法,获得持有读状态的锁的线程数量。不同于写锁,读锁可以同时被多个线程持有。而每个线程持有的读锁支持重入的特性,所以需要对每个线程持有的读锁的数量单独计数,这就需要用到 HoldCounter 计数器

三、HoldCounter 计数器

读锁的内在机制其实就是一个共享锁。一次共享锁的操作就相当于对HoldCounter 计数器的操作。获取共享锁,则该计数器 + 1,释放共享锁,该计数器 - 1。只有当线程获取共享锁后才能对共享锁进行释放、重入操作。

通过 ThreadLocalHoldCounter 类,HoldCounter 与线程进行绑定。HoldCounter 是绑定线程的一个计数器,而 ThreadLocalHoldCounter 则是线程绑定的 ThreadLocal。

  • HoldCounter是用来记录读锁重入数的对象
  • ThreadLocalHoldCounter是ThreadLocal变量,用来存放不是第一个获取读锁的线程的其他线程的读锁重入数对象

四、写锁的获取

写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态。

写锁的获取是通过重写AQS中的tryAcquire方法实现的。

  1. protected final boolean tryAcquire(int acquires) {
  2. //当前线程
  3. Thread current = Thread.currentThread();
  4. //获取state状态 存在读锁或者写锁,状态就不为0
  5. int c = getState();
  6. //获取写锁的重入数
  7. int w = exclusiveCount(c);
  8. //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
  9. if (c != 0) {
  10. // c!=0 && w==0 表示存在读锁
  11. // 当前存在读锁或者写锁已经被其他写线程获取,则写锁获取失败
  12. if (w == 0 || current != getExclusiveOwnerThread())
  13. return false;
  14. // 超出最大范围 65535
  15. if (w + exclusiveCount(acquires) > MAX_COUNT)
  16. throw new Error("Maximum lock count exceeded");
  17. //同步state状态
  18. setState(c + acquires);
  19. return true;
  20. }
  21. // writerShouldBlock有公平与非公平的实现, 非公平返回false,会尝试通过cas加锁
  22. //c==0 写锁未被任何线程获取,当前线程是否阻塞或者cas尝试获取锁
  23. if (writerShouldBlock() ||
  24. !compareAndSetState(c, c + acquires))
  25. return false;
  26. //设置写锁为当前线程所有
  27. setExclusiveOwnerThread(current);
  28. return true;
  29. 复制代码

通过源码我们可以知道:

  • 读写互斥
  • 写写互斥
  • 写锁支持同一个线程重入
  • writerShouldBlock写锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync

五、写锁的释放

写锁释放通过重写AQS的tryRelease方法实现

  1. protected final boolean tryRelease(int releases) {
  2. //若锁的持有者不是当前线程,抛出异常
  3. if (!isHeldExclusively())
  4. throw new IllegalMonitorStateException();
  5. int nextc = getState() - releases;
  6. //当前写状态是否为0,为0则释放写锁
  7. boolean free = exclusiveCount(nextc) == 0;
  8. if (free)
  9. setExclusiveOwnerThread(null);
  10. setState(nextc);
  11. return free;
  12. 复制代码

六、读锁的获取

实现共享式同步组件的同步语义需要通过重写AQS的tryAcquireShared方法和tryReleaseShared方法。读锁的获取实现方法为:

  1. protected final int tryAcquireShared(int unused) {
  2. Thread current = Thread.currentThread();
  3. int c = getState();
  4. // 如果写锁已经被获取并且获取写锁的线程不是当前线程,当前线程获取读锁失败返回-1 判断锁降级
  5. if (exclusiveCount(c) != 0 &&
  6. getExclusiveOwnerThread() != current)
  7. return -1;
  8. //计算出读锁的数量
  9. int r = sharedCount(c);
  10. /**
  11. * 读锁是否阻塞 readerShouldBlock()公平与非公平的实现
  12. * r < MAX_COUNT: 持有读锁的线程小于最大数(65535)
  13. * compareAndSetState(c, c + SHARED_UNIT) cas设置获取读锁线程的数量
  14. */
  15. if (!readerShouldBlock() &&
  16. r < MAX_COUNT &&
  17. compareAndSetState(c, c + SHARED_UNIT)) { //当前线程获取读锁
  18. if (r == 0) { //设置第一个获取读锁的线程
  19. firstReader = current;
  20. firstReaderHoldCount = 1; //设置第一个获取读锁线程的重入数
  21. } else if (firstReader == current) { // 表示第一个获取读锁的线程重入
  22. firstReaderHoldCount++;
  23. } else { // 非第一个获取读锁的线程
  24. HoldCounter rh = cachedHoldCounter;
  25. if (rh == null || rh.tid != getThreadId(current))
  26. cachedHoldCounter = rh = readHolds.get();
  27. else if (rh.count == 0)
  28. readHolds.set(rh);
  29. rh.count++; //记录其他获取读锁的线程的重入次数
  30. }
  31. return 1;
  32. }
  33. // 尝试通过自旋的方式获取读锁,实现了重入逻辑
  34. return fullTryAcquireShared(current);
  35. 复制代码

七、读锁的释放

获取到读锁,执行完临界区后,要记得释放读锁(如果重入多次要释放对应的次数),不然会阻塞其他线程的写操作。

读锁释放的实现主要通过方法tryReleaseShared:

  1. protected final boolean tryReleaseShared(int unused) {
  2. Thread current = Thread.currentThread();
  3. //如果当前线程是第一个获取读锁的线程
  4. if (firstReader == current) {
  5. // assert firstReaderHoldCount > 0;
  6. if (firstReaderHoldCount == 1)
  7. firstReader = null;
  8. else
  9. firstReaderHoldCount--; //重入次数减1
  10. } else { //不是第一个获取读锁的线程
  11. HoldCounter rh = cachedHoldCounter;
  12. if (rh == null || rh.tid != getThreadId(current))
  13. rh = readHolds.get();
  14. int count = rh.count;
  15. if (count <= 1) {
  16. readHolds.remove();
  17. if (count <= 0)
  18. throw unmatchedUnlockException();
  19. }
  20. --rh.count; //重入次数减1
  21. }
  22. for (;;) { //cas更新同步状态
  23. int c = getState();
  24. int nextc = c - SHARED_UNIT;
  25. if (compareAndSetState(c, nextc))
  26. // Releasing the read lock has no effect on readers,
  27. // but it may allow waiting writers to proceed if
  28. // both read and write locks are now free.
  29. return nextc == 0;
  30. }
  31. 复制代码

  • 读锁共享,读读不互斥
  • 读锁可重入,每个获取读锁的线程都会记录对应的重入数
  • 读写互斥,锁降级场景除外
  • 支持锁降级,持有写锁的线程,可以获取读锁,但是后续要记得把读锁和写锁读释放
  • readerShouldBlock读锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/966887
推荐阅读
相关标签
  

闽ICP备14008679号